Commit 9c4eb7f8 authored by Kelvin Fichter's avatar Kelvin Fichter Committed by GitHub

Merge pull request #9 from ethereum-optimism/feat/message-passing-tests

Add tests for message passing contracts
parents 69b705fd b02010d1
node_modules/
artifacts/
cache/
yarn-error.log
build/
# Optimism's Contracts (V2)
This package contains the various Ethereum smart contracts that make up the Layer 1 component of Optimism's Optimistic Rollup construction.
## Building and Running
This package requires that `yarn` be installed on your machine! Once it is, run `yarn test` to build and run tests.
## Disclaimer
The contracts in this package have **not** been audited. We **do not recommend** deploying these contracts in a production capacity.
#!/usr/bin/env python3
# pip3 install pyevmasm
from pyevmasm import instruction_tables
#print(instruction_tables.keys())
def asm(x):
return [instruction_tables['istanbul'][i].opcode for i in x]
push_opcodes = asm(["PUSH%d" % i for i in range(1,33)])
stop_opcodes = asm(["STOP", "JUMP", "RETURN", "INVALID"])
caller_opcodes = asm(["CALLER"])
blacklist_ops = set([
"ADDRESS", "BALANCE", "BLOCKHASH",
"CALL", "CALLCODE", "CHAINID", "COINBASE",
"CREATE", "CREATE2", "DELEGATECALL", "DIFFICULTY",
"EXTCODESIZE", "EXTCODECOPY", "EXTCODEHASH",
"GASLIMIT", "GASPRICE", "NUMBER",
"ORIGIN", "REVERT", "SELFBALANCE", "SELFDESTRUCT",
"SLOAD", "SSTORE", "STATICCALL", "TIMESTAMP"])
whitelist_opcodes = []
for x in instruction_tables['istanbul']:
if x.name not in blacklist_ops:
whitelist_opcodes.append(x.opcode)
pushmask = 0
for x in push_opcodes:
pushmask |= 1 << x
stopmask = 0
for x in stop_opcodes:
stopmask |= 1 << x
stoplist = [0]*256
procmask = 0
for i in range(256):
if i in whitelist_opcodes and \
i not in push_opcodes and \
i not in stop_opcodes and \
i not in caller_opcodes:
# can skip this opcode
stoplist[i] = 1
else:
procmask |= 1 << i
# PUSH1 through PUSH4, can't skip in slow
for i in range(0x60, 0x64):
stoplist[i] = i-0x5e
rr = "uint256[8] memory opcodeSkippableBytes = [\n"
for i in range(0, 0x100, 0x20):
ret = "uint256(0x"
for j in range(i, i+0x20, 1):
ret += ("%02X" % stoplist[j])
rr += ret+"),\n"
rr = rr[:-2] + "];"
print(rr)
print("// Mask to gate opcode specific cases")
print("uint256 opcodeGateMask = ~uint256(0x%x);" % procmask)
print("// Halting opcodes")
print("uint256 opcodeHaltingMask = ~uint256(0x%x);" % stopmask)
print("// PUSH opcodes")
print("uint256 opcodePushMask = ~uint256(0x%x);" % pushmask)
/* External Imports */
import * as fs from 'fs'
import * as path from 'path'
import * as mkdirp from 'mkdirp'
/* Internal Imports */
import { makeStateDump } from '../src/contract-dumps'
;(async () => {
const outdir = path.resolve(__dirname, '../build/dumps')
const outfile = path.join(outdir, 'state-dump.latest.json')
mkdirp.sync(outdir)
const dump = await makeStateDump()
fs.writeFileSync(outfile, JSON.stringify(dump, null, 4))
})()
import { usePlugin, BuidlerConfig } from '@nomiclabs/buidler/config'
import { DEFAULT_ACCOUNTS_BUIDLER, GAS_LIMIT } from './test/helpers/constants'
import {
DEFAULT_ACCOUNTS_BUIDLER,
RUN_OVM_TEST_GAS,
} from './test/helpers/constants'
usePlugin('@nomiclabs/buidler-ethers')
usePlugin('@nomiclabs/buidler-waffle')
import './test/helpers/buidler/modify-compiler'
import '@eth-optimism/smock/build/src/buidler-plugins/compiler-storage-layout'
const config: BuidlerConfig = {
networks: {
buidlerevm: {
accounts: DEFAULT_ACCOUNTS_BUIDLER,
blockGasLimit: GAS_LIMIT * 2,
blockGasLimit: RUN_OVM_TEST_GAS * 2,
},
},
mocha: {
......
......@@ -4,11 +4,11 @@ pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContractAccount.sol";
import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManager.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_ECDSAUtils } from "../../libraries/utils/Lib_ECDSAUtils.sol";
import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol";
/**
* @title OVM_ECDSAContractAccount
......@@ -43,7 +43,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
bytes memory _returndata
)
{
iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(msg.sender);
address ovmExecutionManager = msg.sender;
// Address of this contract within the ovm (ovmADDRESS) should be the same as the
// recovered address of the user who signed this message. This is how we manage to shim
......@@ -55,8 +55,8 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
_v,
_r,
_s,
ovmExecutionManager.ovmCHAINID()
) == ovmExecutionManager.ovmADDRESS(),
Lib_SafeExecutionManagerWrapper.safeCHAINID(ovmExecutionManager)
) == Lib_SafeExecutionManagerWrapper.safeADDRESS(ovmExecutionManager),
"Signature provided for EOA transaction execution is invalid."
);
......@@ -64,14 +64,15 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// Need to make sure that the transaction nonce is right and bump it if so.
require(
decodedTx.nonce == ovmExecutionManager.ovmGETNONCE() + 1,
decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager) + 1,
"Transaction nonce does not match the expected nonce."
);
ovmExecutionManager.ovmSETNONCE(decodedTx.nonce);
// Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.target == address(0)) {
address created = ovmExecutionManager.ovmCREATE{gas: decodedTx.gasLimit}(
address created = Lib_SafeExecutionManagerWrapper.safeCREATE(
ovmExecutionManager,
decodedTx.gasLimit,
decodedTx.data
);
......@@ -79,7 +80,13 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// initialization. Always return `true` for our success value here.
return (true, abi.encode(created));
} else {
return ovmExecutionManager.ovmCALL(
// We only want to bump the nonce for `ovmCALL` because `ovmCREATE` automatically bumps
// the nonce of the calling account. Normally an EOA would bump the nonce for both
// cases, but since this is a contract we'd end up bumping the nonce twice.
Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, decodedTx.nonce);
return Lib_SafeExecutionManagerWrapper.safeCALL(
ovmExecutionManager,
decodedTx.gasLimit,
decodedTx.target,
decodedTx.data
......
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_BaseCrossDomainMessenger } from "../../iOVM/bridge/iOVM_BaseCrossDomainMessenger.sol";
/**
* @title OVM_BaseCrossDomainMessenger
*/
contract OVM_BaseCrossDomainMessenger is iOVM_BaseCrossDomainMessenger {
/**********************
* Contract Variables *
**********************/
mapping (bytes32 => bool) public receivedMessages;
mapping (bytes32 => bool) public sentMessages;
uint256 public messageNonce;
address public xDomainMessageSender;
/********************
* Public Functions *
********************/
/**
* Sends a cross domain message to the target messenger.
* @param _target Target contract address.
* @param _message Message to send to the target.
* @param _gasLimit Gas limit for the provided message.
*/
function sendMessage(
address _target,
bytes memory _message,
uint256 _gasLimit
)
override
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
msg.sender,
_message,
messageNonce
);
_sendXDomainMessage(xDomainCalldata, _gasLimit);
messageNonce += 1;
sentMessages[keccak256(xDomainCalldata)] = true;
}
/**********************
* Internal Functions *
**********************/
/**
* Generates the correct cross domain calldata for a message.
* @param _target Target contract address.
* @param _sender Message sender address.
* @param _message Message to send to the target.
* @param _messageNonce Nonce for the provided message.
* @return ABI encoded cross domain calldata.
*/
function _getXDomainCalldata(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
)
internal
pure
returns (
bytes memory
)
{
return abi.encodeWithSignature(
"relayMessage(address,address,bytes,uint256)",
_target,
_sender,
_message,
_messageNonce
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit Gas limit for the provided message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint256 _gasLimit
)
virtual
internal
{
revert("Implement me in child contracts!");
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
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_SecureMerkleTrie } from "../../libraries/trie/Lib_SecureMerkleTrie.sol";
import { Lib_BytesUtils } from "../../libraries/utils/Lib_BytesUtils.sol";
/* Interface Imports */
import { iOVM_L1CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L1CrossDomainMessenger.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
import { iOVM_StateCommitmentChain } from "../../iOVM/chain/iOVM_StateCommitmentChain.sol";
/* Contract Imports */
import { OVM_BaseCrossDomainMessenger } from "./OVM_BaseCrossDomainMessenger.sol";
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
/**
* @title OVM_L1CrossDomainMessenger
*/
contract OVM_L1CrossDomainMessenger is iOVM_L1CrossDomainMessenger, OVM_BaseCrossDomainMessenger, Lib_AddressResolver {
/*******************************************
* Contract Variables: Contract References *
*******************************************/
iOVM_CanonicalTransactionChain internal ovmCanonicalTransactionChain;
iOVM_StateCommitmentChain internal ovmStateCommitmentChain;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address of the Address Manager.
*/
constructor(
address _libAddressManager
)
Lib_AddressResolver(_libAddressManager)
{
ovmCanonicalTransactionChain = iOVM_CanonicalTransactionChain(resolve("OVM_CanonicalTransactionChain"));
ovmStateCommitmentChain = iOVM_StateCommitmentChain(resolve("OVM_StateCommitmentChain"));
}
/********************
* Public Functions *
********************/
/**
* Relays a cross domain message to a contract.
* @inheritdoc iOVM_L1CrossDomainMessenger
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
L2MessageInclusionProof memory _proof
)
override
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
require(
_verifyXDomainMessage(
xDomainCalldata,
_proof
) == true,
"Provided message could not be verified."
);
require(
receivedMessages[keccak256(xDomainCalldata)] == false,
"Provided message has already been received."
);
xDomainMessageSender = _sender;
_target.call(_message);
// Messages are considered successfully executed if they complete
// without running out of gas (revert or not). As a result, we can
// ignore the result of the call and always mark the message as
// successfully executed because we won't get here unless we have
// enough gas left over.
receivedMessages[keccak256(xDomainCalldata)] = true;
}
/**
* Replays a cross domain message to the target messenger.
* @inheritdoc iOVM_L1CrossDomainMessenger
*/
function replayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
uint32 _gasLimit
)
override
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
require(
sentMessages[keccak256(xDomainCalldata)] == true,
"Provided message has not already been sent."
);
_sendXDomainMessage(xDomainCalldata, _gasLimit);
}
/**********************
* Internal Functions *
**********************/
/**
* Verifies that the given message is valid.
* @param _xDomainCalldata Calldata to verify.
* @param _proof Inclusion proof for the message.
* @return Whether or not the provided message is valid.
*/
function _verifyXDomainMessage(
bytes memory _xDomainCalldata,
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
return (
_verifyStateRootProof(_proof)
&& _verifyStorageProof(_xDomainCalldata, _proof)
);
}
/**
* Verifies that the state root within an inclusion proof is valid.
* @param _proof Message inclusion proof.
* @return Whether or not the provided proof is valid.
*/
function _verifyStateRootProof(
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
return (
ovmStateCommitmentChain.insideFraudProofWindow(_proof.stateRootBatchHeader) == false
&& ovmStateCommitmentChain.verifyElement(
abi.encodePacked(_proof.stateRoot),
_proof.stateRootBatchHeader,
_proof.stateRootProof
)
);
}
/**
* Verifies that the storage proof within an inclusion proof is valid.
* @param _xDomainCalldata Encoded message calldata.
* @param _proof Message inclusion proof.
* @return Whether or not the provided proof is valid.
*/
function _verifyStorageProof(
bytes memory _xDomainCalldata,
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
bytes32 storageKey = keccak256(
Lib_BytesUtils.concat(
abi.encodePacked(keccak256(_xDomainCalldata)),
abi.encodePacked(uint256(0))
)
);
(
bool exists,
bytes memory encodedMessagePassingAccount
) = Lib_SecureMerkleTrie.get(
abi.encodePacked(0x4200000000000000000000000000000000000000),
_proof.stateTrieWitness,
_proof.stateRoot
);
require(
exists == true,
"Message passing precompile has not been initialized or invalid proof provided."
);
Lib_OVMCodec.EVMAccount memory account = Lib_OVMCodec.decodeEVMAccount(
encodedMessagePassingAccount
);
return Lib_SecureMerkleTrie.verifyInclusionProof(
abi.encodePacked(storageKey),
abi.encodePacked(uint256(1)),
_proof.storageTrieWitness,
account.storageRoot
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit OVM gas limit for the message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint256 _gasLimit
)
override
internal
{
ovmCanonicalTransactionChain.enqueue(
resolve("OVM_L2CrossDomainMessenger"),
_gasLimit,
_message
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
/* Interface Imports */
import { iOVM_L2CrossDomainMessenger } from "../../iOVM/bridge/iOVM_L2CrossDomainMessenger.sol";
import { iOVM_L1MessageSender } from "../../iOVM/precompiles/iOVM_L1MessageSender.sol";
import { iOVM_L2ToL1MessagePasser } from "../../iOVM/precompiles/iOVM_L2ToL1MessagePasser.sol";
/* Contract Imports */
import { OVM_BaseCrossDomainMessenger } from "./OVM_BaseCrossDomainMessenger.sol";
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
/**
* @title OVM_L2CrossDomainMessenger
*/
contract OVM_L2CrossDomainMessenger is iOVM_L2CrossDomainMessenger, OVM_BaseCrossDomainMessenger, Lib_AddressResolver {
/*******************************************
* Contract Variables: Contract References *
*******************************************/
iOVM_L1MessageSender internal ovmL1MessageSender;
iOVM_L2ToL1MessagePasser internal ovmL2ToL1MessagePasser;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address of the Address Manager.
*/
constructor(
address _libAddressManager
)
Lib_AddressResolver(_libAddressManager)
{
ovmL1MessageSender = iOVM_L1MessageSender(resolve("OVM_L1MessageSender"));
ovmL2ToL1MessagePasser = iOVM_L2ToL1MessagePasser(resolve("OVM_L2ToL1MessagePasser"));
}
/********************
* Public Functions *
********************/
/**
* Relays a cross domain message to a contract.
* @inheritdoc iOVM_L2CrossDomainMessenger
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
)
override
public
{
require(
_verifyXDomainMessage() == true,
"Provided message could not be verified."
);
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
require(
receivedMessages[keccak256(xDomainCalldata)] == false,
"Provided message has already been received."
);
xDomainMessageSender = _sender;
_target.call(_message);
// Messages are considered successfully executed if they complete
// without running out of gas (revert or not). As a result, we can
// ignore the result of the call and always mark the message as
// successfully executed because we won't get here unless we have
// enough gas left over.
receivedMessages[keccak256(xDomainCalldata)] = true;
}
/**********************
* Internal Functions *
**********************/
/**
* Verifies that a received cross domain message is valid.
* @return _valid Whether or not the message is valid.
*/
function _verifyXDomainMessage()
internal
returns (
bool _valid
)
{
return (
ovmL1MessageSender.getL1MessageSender() == resolve("OVM_L1CrossDomainMessenger")
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit Gas limit for the provided message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint256 _gasLimit
)
override
internal
{
ovmL2ToL1MessagePasser.passMessageToL1(_message);
}
}
......@@ -8,6 +8,7 @@ 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
......@@ -18,11 +19,23 @@ contract OVM_BaseChain is iOVM_BaseChain {
* Contract Variables: Batches *
*******************************/
bytes32[] internal 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 *
*************************************/
......@@ -32,6 +45,7 @@ contract OVM_BaseChain is iOVM_BaseChain {
* @return _totalElements Total submitted elements.
*/
function getTotalElements()
virtual
override
public
view
......@@ -39,7 +53,7 @@ contract OVM_BaseChain is iOVM_BaseChain {
uint256 _totalElements
)
{
return totalElements;
return uint256(uint224(batches.getExtraData()));
}
/**
......@@ -54,7 +68,7 @@ contract OVM_BaseChain is iOVM_BaseChain {
uint256 _totalBatches
)
{
return totalBatches;
return uint256(batches.getLength());
}
......@@ -82,7 +96,7 @@ contract OVM_BaseChain is iOVM_BaseChain {
)
{
require(
_hashBatchHeader(_batchHeader) == batches[_batchHeader.batchIndex],
_hashBatchHeader(_batchHeader) == batches.get(uint32(_batchHeader.batchIndex)),
"Invalid batch header."
);
......@@ -114,9 +128,7 @@ contract OVM_BaseChain is iOVM_BaseChain {
internal
{
bytes32 batchHeaderHash = _hashBatchHeader(_batchHeader);
batches.push(batchHeaderHash);
totalBatches += 1;
totalElements += _batchHeader.batchSize;
batches.push(batchHeaderHash, bytes28(uint224(getTotalElements() + _batchHeader.batchSize)));
}
/**
......@@ -131,7 +143,7 @@ contract OVM_BaseChain is iOVM_BaseChain {
internal
{
Lib_OVMCodec.ChainBatchHeader memory batchHeader = Lib_OVMCodec.ChainBatchHeader({
batchIndex: batches.length,
batchIndex: uint(batches.getLength()),
batchRoot: Lib_MerkleUtils.getMerkleRoot(_elements),
batchSize: _elements.length,
prevTotalElements: totalElements,
......@@ -166,24 +178,19 @@ contract OVM_BaseChain is iOVM_BaseChain {
internal
{
require(
_batchHeader.batchIndex < batches.length,
_batchHeader.batchIndex < batches.getLength(),
"Invalid batch index."
);
require(
_hashBatchHeader(_batchHeader) == batches[_batchHeader.batchIndex],
_hashBatchHeader(_batchHeader) == batches.get(uint32(_batchHeader.batchIndex)),
"Invalid batch header."
);
totalBatches = _batchHeader.batchIndex;
totalElements = _batchHeader.prevTotalElements;
batches.deleteElementsAfter(uint32(_batchHeader.batchIndex - 1), bytes28(uint224(totalElements)));
}
/*********************
* Private Functions *
*********************/
/**
* Calculates a hash for a given batch header.
* @param _batchHeader Header to hash.
......@@ -192,7 +199,7 @@ contract OVM_BaseChain is iOVM_BaseChain {
function _hashBatchHeader(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
private
internal
pure
returns (
bytes32 _hash
......
......@@ -2,38 +2,48 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Proxy Imports */
import { Proxy_Resolver } from "../../proxy/Proxy_Resolver.sol";
/* 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_MerkleRoot } from "../../libraries/utils/Lib_MerkleRoot.sol";
import { TimeboundRingBuffer, Lib_TimeboundRingBuffer } from "../../libraries/utils/Lib_TimeboundRingBuffer.sol";
/* Interface Imports */
import { iOVM_BaseChain } from "../../iOVM/chain/iOVM_BaseChain.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
import { iOVM_L1ToL2TransactionQueue } from "../../iOVM/queue/iOVM_L1ToL2TransactionQueue.sol";
/* Contract Imports */
import { OVM_BaseChain } from "./OVM_BaseChain.sol";
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
/**
* @title OVM_CanonicalTransactionChain
*/
contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_BaseChain, Proxy_Resolver {
contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_BaseChain, Lib_AddressResolver {
/*******************************************
* Contract Variables: Contract References *
*******************************************/
/*************
* Constants *
*************/
iOVM_L1ToL2TransactionQueue internal ovmL1ToL2TransactionQueue;
uint256 constant public MIN_ROLLUP_TX_GAS = 20000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 10000;
uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 10;
/*******************************************
* Contract Variables: Internal Accounting *
*******************************************/
/*************
* Variables *
*************/
uint256 internal forceInclusionPeriodSeconds;
uint256 internal lastOVMTimestamp;
address internal sequencer;
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer internal queue;
TimeboundRingBuffer internal chain;
/***************
......@@ -41,117 +51,418 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
***************/
/**
* @param _proxyManager Address of the Proxy_Manager.
* @param _libAddressManager Address of the Address Manager.
* @param _forceInclusionPeriodSeconds Period during which only the sequencer can submit.
*/
constructor(
address _proxyManager,
address _libAddressManager,
uint256 _forceInclusionPeriodSeconds
)
Proxy_Resolver(_proxyManager)
Lib_AddressResolver(_libAddressManager)
{
ovmL1ToL2TransactionQueue = iOVM_L1ToL2TransactionQueue(resolve("OVM_L1ToL2TransactionQueue"));
sequencer = resolve("OVM_Sequencer");
forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds;
queue.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
batches.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
}
/****************************************
* Public Functions: Batch Manipulation *
****************************************/
/********************
* Public Functions *
********************/
function getTotalElements()
override(OVM_BaseChain, iOVM_BaseChain)
public
view
returns (
uint256 _totalElements
)
{
(uint40 totalElements,) = _getLatestBatchContext();
return uint256(totalElements);
}
/**
* Appends a batch from the L1ToL2TransactionQueue.
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendQueueBatch()
function getQueueElement(
uint256 _index
)
override
public
view
returns (
Lib_OVMCodec.QueueElement memory _element
)
{
uint32 trueIndex = uint32(_index * 2);
bytes32 queueRoot = queue.get(trueIndex);
bytes32 timestampAndBlockNumber = queue.get(trueIndex + 1);
uint40 elementTimestamp;
uint32 elementBlockNumber;
assembly {
elementTimestamp := and(timestampAndBlockNumber, 0x000000000000000000000000000000000000000000000000000000ffffffffff)
elementBlockNumber := shr(40, and(timestampAndBlockNumber, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000))
}
return Lib_OVMCodec.QueueElement({
queueRoot: queueRoot,
timestamp: elementTimestamp,
blockNumber: elementBlockNumber
});
}
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function enqueue(
address _target,
uint256 _gasLimit,
bytes memory _data
)
override
public
{
require(
_data.length <= MAX_ROLLUP_TX_SIZE,
"Transaction exceeds maximum rollup data size."
);
require(
ovmL1ToL2TransactionQueue.size() > 0,
"No batches are currently queued to be appended."
_gasLimit >= MIN_ROLLUP_TX_GAS,
"Layer 2 gas limit too low to enqueue."
);
Lib_OVMCodec.QueueElement memory queueElement = ovmL1ToL2TransactionQueue.peek();
uint256 gasToConsume = _gasLimit/L2_GAS_DISCOUNT_DIVISOR;
uint256 startingGas = gasleft();
// Although this check is not necessary (burn below will run out of gas if not true), it
// gives the user an explicit reason as to why the enqueue attempt failed.
require(
queueElement.timestamp + forceInclusionPeriodSeconds <= block.timestamp,
"Cannot append until the inclusion delay period has elapsed."
startingGas > gasToConsume,
"Insufficient gas for L2 rate limiting burn."
);
// We need to consume some amount of L1 gas in order to rate limit transactions going into
// L2. However, L2 is cheaper than L1 so we only need to burn some small proportion of the
// provided L1 gas.
//
// Here we do some "dumb" work in order to burn gas, although we should probably replace
// this with something like minting gas token later on.
uint256 i;
while(startingGas - gasleft() < gasToConsume) {
i++;
}
bytes memory transaction = abi.encode(
msg.sender,
_target,
_gasLimit,
_data
);
_appendQueueBatch(queueElement, 1);
ovmL1ToL2TransactionQueue.dequeue();
bytes32 transactionHash = keccak256(transaction);
bytes32 timestampAndBlockNumber;
assembly {
timestampAndBlockNumber := or(timestamp(), shl(40, number()))
}
queue.push2(transactionHash, timestampAndBlockNumber, bytes28(0));
emit QueueTransactionAppended(transaction, timestampAndBlockNumber);
}
/**
* Appends a sequencer batch.
* @param _batch Batch of transactions to append.
* @param _timestamp Timestamp for the provided batch.
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendQueueBatch(
uint _numQueuedTransactions
)
override
public
{
require(
_numQueuedTransactions > 0,
"Must append more than zero transactions."
);
(uint40 totalElements, uint32 nextQueueIndex) = _getLatestBatchContext();
bytes32[] memory leaves = new bytes32[](_numQueuedTransactions);
for (uint i = 0; i < _numQueuedTransactions; i++) {
leaves[i] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
}
_appendBatch(
Lib_MerkleRoot.getMerkleRoot(leaves),
_numQueuedTransactions,
_numQueuedTransactions
);
emit ChainBatchAppended(
nextQueueIndex - _numQueuedTransactions,
_numQueuedTransactions
);
}
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendSequencerBatch(
bytes[] memory _batch,
uint256 _timestamp
bytes[] memory _transactions,
BatchContext[] memory _contexts,
uint256 _shouldStartAtBatch,
uint _totalElementsToAppend
)
override
public
{
require(
msg.sender == resolve("Sequencer"),
_shouldStartAtBatch == getTotalBatches(),
"Actual batch start index does not match expected start index."
);
require(
msg.sender == sequencer,
"Function can only be called by the Sequencer."
);
require(
_batch.length > 0,
"Cannot submit an empty batch."
_contexts.length > 0,
"Must provide at least one batch context."
);
require(
_timestamp > lastOVMTimestamp,
"Batch timestamp must be later than the last OVM timestamp."
_totalElementsToAppend > 0,
"Must append at least one element."
);
bytes32[] memory leaves = new bytes32[](_totalElementsToAppend);
uint32 transactionIndex = 0;
uint32 numSequencerTransactionsProcessed = 0;
(, uint32 nextQueueIndex) = _getLatestBatchContext();
for (uint32 i = 0; i < _contexts.length; i++) {
BatchContext memory context = _contexts[i];
_validateBatchContext(context, nextQueueIndex);
for (uint32 i = 0; i < context.numSequencedTransactions; i++) {
leaves[transactionIndex] = _hashTransactionChainElement(
TransactionChainElement({
isSequenced: true,
queueIndex: 0,
timestamp: context.timestamp,
blockNumber: context.blockNumber,
txData: _transactions[numSequencerTransactionsProcessed]
})
);
numSequencerTransactionsProcessed++;
transactionIndex++;
}
for (uint32 i = 0; i < context.numSubsequentQueueTransactions; i++) {
leaves[transactionIndex] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
transactionIndex++;
}
}
if (ovmL1ToL2TransactionQueue.size() > 0) {
require(
_timestamp <= ovmL1ToL2TransactionQueue.peek().timestamp,
"Older queue batches must be processed before a newer sequencer batch."
transactionIndex == _totalElementsToAppend,
"Actual transaction index does not match expected total elements to append."
);
uint256 numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed;
_appendBatch(
Lib_MerkleRoot.getMerkleRoot(leaves),
_totalElementsToAppend,
numQueuedTransactions
);
emit ChainBatchAppended(
nextQueueIndex - numQueuedTransactions,
numQueuedTransactions
);
}
Lib_OVMCodec.QueueElement memory queueElement = Lib_OVMCodec.QueueElement({
timestamp: _timestamp,
batchRoot: Lib_MerkleUtils.getMerkleRoot(_batch),
isL1ToL2Batch: false
});
_appendQueueBatch(queueElement, _batch.length);
/**********************
* Internal Functions *
**********************/
/**
* Parses the batch context from the extra data.
* @return _totalElements Total number of elements submitted.
* @return _nextQueueIndex Index of the next queue element.
*/
function _getLatestBatchContext()
internal
view
returns (
uint40 _totalElements,
uint32 _nextQueueIndex
)
{
bytes28 extraData = batches.getExtraData();
uint40 totalElements;
uint32 nextQueueIndex;
assembly {
totalElements := and(shr(32, extraData), 0x000000000000000000000000000000000000000000000000000000ffffffffff)
nextQueueIndex := shr(40, and(shr(32, extraData), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000))
}
return (totalElements, nextQueueIndex);
}
/**
* Encodes the batch context for the extra data.
* @param _totalElements Total number of elements submitted.
* @param _nextQueueIndex Index of the next queue element.
* @return _context Encoded batch context.
*/
function _makeLatestBatchContext(
uint40 _totalElements,
uint32 _nextQueueIndex
)
internal
view
returns (
bytes28 _context
)
{
bytes28 totalElementsAndNextQueueIndex;
assembly {
totalElementsAndNextQueueIndex := shl(32, or(_totalElements, shl(40, _nextQueueIndex)))
}
return totalElementsAndNextQueueIndex;
}
/**
* Retrieves the hash of a queue element.
* @param _index Index of the queue element to retrieve a hash for.
* @return _queueLeafHash Hash of the queue element.
*/
function _getQueueLeafHash(
uint _index
)
internal
view
returns (
bytes32 _queueLeafHash
)
{
Lib_OVMCodec.QueueElement memory element = getQueueElement(_index);
require(
msg.sender == sequencer
|| element.timestamp + forceInclusionPeriodSeconds <= block.timestamp,
"Queue transactions cannot be submitted during the sequencer inclusion period."
);
/******************************************
* Internal Functions: Batch Manipulation *
******************************************/
return _hashTransactionChainElement(
TransactionChainElement({
isSequenced: false,
queueIndex: _index,
timestamp: 0,
blockNumber: 0,
txData: hex""
})
);
}
/**
* Appends a queue batch to the chain.
* @param _queueElement Queue element to append.
* Inserts a batch into the chain of batches.
* @param _transactionRoot Root of the transaction tree for this batch.
* @param _batchSize Number of elements in the batch.
* @param _numQueuedTransactions Number of queue transactions in the batch.
*/
function _appendQueueBatch(
Lib_OVMCodec.QueueElement memory _queueElement,
uint256 _batchSize
function _appendBatch(
bytes32 _transactionRoot,
uint _batchSize,
uint _numQueuedTransactions
)
internal
{
Lib_OVMCodec.ChainBatchHeader memory batchHeader = Lib_OVMCodec.ChainBatchHeader({
batchIndex: getTotalBatches(),
batchRoot: _queueElement.batchRoot,
(uint40 totalElements, uint32 nextQueueIndex) = _getLatestBatchContext();
Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
batchIndex: batches.getLength(),
batchRoot: _transactionRoot,
batchSize: _batchSize,
prevTotalElements: getTotalElements(),
extraData: abi.encodePacked(
_queueElement.timestamp,
_queueElement.isL1ToL2Batch
)
prevTotalElements: totalElements,
extraData: hex""
});
_appendBatch(batchHeader);
lastOVMTimestamp = _queueElement.timestamp;
bytes32 batchHeaderHash = _hashBatchHeader(header);
bytes28 latestBatchContext = _makeLatestBatchContext(
totalElements + uint40(header.batchSize),
nextQueueIndex + uint32(_numQueuedTransactions)
);
batches.push(batchHeaderHash, latestBatchContext);
}
/**
* Checks that a given batch context is valid.
* @param _context Batch context to validate.
* @param _nextQueueIndex Index of the next queue element to process.
*/
function _validateBatchContext(
BatchContext memory _context,
uint32 _nextQueueIndex
)
internal
{
if (queue.getLength() == 0) {
return;
}
Lib_OVMCodec.QueueElement memory nextQueueElement = getQueueElement(_nextQueueIndex);
require(
block.timestamp < nextQueueElement.timestamp + forceInclusionPeriodSeconds,
"Older queue batches must be processed before a new sequencer batch."
);
require(
_context.timestamp <= nextQueueElement.timestamp,
"Sequencer transactions timestamp too high."
);
require(
_context.blockNumber <= nextQueueElement.blockNumber,
"Sequencer transactions blockNumber too high."
);
}
/**
* Hashes a transaction chain element.
* @param _element Chain element to hash.
* @return _hash Hash of the chain element.
*/
function _hashTransactionChainElement(
TransactionChainElement memory _element
)
internal
pure
returns (
bytes32 _hash
)
{
return keccak256(
abi.encode(
_element.isSequenced,
_element.queueIndex,
_element.timestamp,
_element.blockNumber,
_element.txData
)
);
}
}
......@@ -2,11 +2,9 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Proxy Imports */
import { Proxy_Resolver } from "../../proxy/Proxy_Resolver.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
/* Interface Imports */
import { iOVM_FraudVerifier } from "../../iOVM/verification/iOVM_FraudVerifier.sol";
......@@ -19,7 +17,14 @@ import { OVM_BaseChain } from "./OVM_BaseChain.sol";
/**
* @title OVM_StateCommitmentChain
*/
contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, Proxy_Resolver {
contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, Lib_AddressResolver {
/*************
* Constants *
*************/
uint256 constant public FRAUD_PROOF_WINDOW = 7 days;
/*******************************************
* Contract Variables: Contract References *
......@@ -34,12 +39,12 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, P
***************/
/**
* @param _proxyManager Address of the Proxy_Manager.
* @param _libAddressManager Address of the Address Manager.
*/
constructor(
address _proxyManager
address _libAddressManager
)
Proxy_Resolver(_proxyManager)
Lib_AddressResolver(_libAddressManager)
{
ovmCanonicalTransactionChain = iOVM_CanonicalTransactionChain(resolve("OVM_CanonicalTransactionChain"));
ovmFraudVerifier = iOVM_FraudVerifier(resolve("OVM_FraudVerifier"));
......@@ -75,7 +80,12 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, P
elements[i] = abi.encodePacked(_batch[i]);
}
_appendBatch(elements);
_appendBatch(
elements,
abi.encode(
block.timestamp
)
);
}
/**
......@@ -93,6 +103,39 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, P
"State batches can only be deleted by the OVM_FraudVerifier."
);
require(
insideFraudProofWindow(_batchHeader),
"State batches can only be deleted within the fraud proof window."
);
_deleteBatch(_batchHeader);
}
/**********************************
* Public Functions: Batch Status *
**********************************/
function insideFraudProofWindow(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
override
public
view
returns (
bool _inside
)
{
uint256 timestamp = abi.decode(
_batchHeader.extraData,
(uint256)
);
require(
timestamp != 0,
"Batch header timestamp cannot be zero"
);
return timestamp + FRAUD_PROOF_WINDOW > block.timestamp;
}
}
......@@ -4,6 +4,7 @@ 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_EthUtils } from "../../libraries/utils/Lib_EthUtils.sol";
/* Interface Imports */
......@@ -20,14 +21,14 @@ import { console } from "@nomiclabs/buidler/console.sol";
/**
* @title OVM_ExecutionManager
*/
contract OVM_ExecutionManager is iOVM_ExecutionManager {
contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
/********************************
* External Contract References *
********************************/
iOVM_SafetyChecker public ovmSafetyChecker;
iOVM_StateManager public ovmStateManager;
iOVM_SafetyChecker internal ovmSafetyChecker;
iOVM_StateManager internal ovmStateManager;
/*******************************
......@@ -49,6 +50,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
address constant GAS_METADATA_ADDRESS = 0x06a506A506a506A506a506a506A506A506A506A5;
uint256 constant NUISANCE_GAS_SLOAD = 20000;
uint256 constant NUISANCE_GAS_SSTORE = 20000;
uint256 constant MIN_NUISANCE_GAS_PER_CONTRACT = 30000;
uint256 constant NUISANCE_GAS_PER_CONTRACT_BYTE = 100;
uint256 constant MIN_GAS_FOR_INVALID_STATE_ACCESS = 30000;
......@@ -58,12 +60,18 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
***************/
/**
* @param _ovmSafetyChecker Address of the iOVM_SafetyChecker implementation.
* @param _libAddressManager Address of the Address Manager.
*/
constructor(
address _ovmSafetyChecker
) {
ovmSafetyChecker = iOVM_SafetyChecker(_ovmSafetyChecker);
address _libAddressManager,
GasMeterConfig memory _gasMeterConfig,
GlobalContext memory _globalContext
)
Lib_AddressResolver(_libAddressManager)
{
ovmSafetyChecker = iOVM_SafetyChecker(resolve("OVM_SafetyChecker"));
gasMeterConfig = _gasMeterConfig;
globalContext = _globalContext;
}
......@@ -116,16 +124,24 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
override
public
{
// Store our OVM_StateManager instance (significantly easier than attempting to pass the address
// around in calldata).
// Store our OVM_StateManager instance (significantly easier than attempting to pass the
// address around in calldata).
ovmStateManager = iOVM_StateManager(_ovmStateManager);
// Make sure this function can't be called by anyone except the owner of the
// OVM_StateManager (expected to be an OVM_StateTransitioner). We can revert here because
// this would make the `run` itself invalid.
require(
msg.sender == ovmStateManager.owner(),
"Only the owner of the ovmStateManager can call this function"
);
// Check whether we need to start a new epoch, do so if necessary.
_checkNeedsNewEpoch(_transaction.timestamp);
// Make sure the transaction's gas limit is valid. We don't revert here because we reserve
// reverts for INVALID_STATE_ACCESS.
if (_isValidGasLimit(_transaction.gasLimit, _transaction.queueOrigin) == false) {
if (_isValidGasLimit(_transaction.gasLimit, _transaction.l1QueueOrigin) == false) {
return;
}
......@@ -142,10 +158,13 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
uint256 gasUsed = gasProvided - gasleft();
// Update the cumulative gas based on the amount of gas used.
_updateCumulativeGas(gasUsed, _transaction.queueOrigin);
_updateCumulativeGas(gasUsed, _transaction.l1QueueOrigin);
// Wipe the execution context.
_resetContext();
// Reset the ovmStateManager.
ovmStateManager = iOVM_StateManager(address(0));
}
......@@ -184,33 +203,33 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
}
/**
* @notice Overrides ORIGIN.
* @return _ORIGIN Address of the ORIGIN within the transaction context.
* @notice Overrides TIMESTAMP.
* @return _TIMESTAMP Value of the TIMESTAMP within the transaction context.
*/
function ovmORIGIN()
function ovmTIMESTAMP()
override
public
view
returns (
address _ORIGIN
uint256 _TIMESTAMP
)
{
return transactionContext.ovmORIGIN;
return transactionContext.ovmTIMESTAMP;
}
/**
* @notice Overrides TIMESTAMP.
* @return _TIMESTAMP Value of the TIMESTAMP within the transaction context.
* @notice Overrides NUMBER.
* @return _NUMBER Value of the NUMBER within the transaction context.
*/
function ovmTIMESTAMP()
function ovmNUMBER()
override
public
view
returns (
uint256 _TIMESTAMP
uint256 _NUMBER
)
{
return transactionContext.ovmTIMESTAMP;
return transactionContext.ovmNUMBER;
}
/**
......@@ -243,6 +262,39 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
return globalContext.ovmCHAINID;
}
/*********************************
* Opcodes: L2 Execution Context *
*********************************/
/**
* @notice Specifies from which L1 rollup queue this transaction originated from.
* @return _queueOrigin Address of the CALLER within the current message context.
*/
function ovmL1QUEUEORIGIN()
override
public
view
returns (
Lib_OVMCodec.QueueOrigin _queueOrigin
)
{
return transactionContext.ovmL1QUEUEORIGIN;
}
/**
* @notice Specifies what L1 EOA, if any, sent this transaction.
* @return _l1TxOrigin Address of the EOA which send the tx into L2 from L1.
*/
function ovmL1TXORIGIN()
override
public
view
returns (
address _l1TxOrigin
)
{
return transactionContext.ovmL1TXORIGIN;
}
/********************
* Opcodes: Halting *
......@@ -457,7 +509,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
MessageContext memory nextMessageContext = messageContext;
nextMessageContext.ovmCALLER = nextMessageContext.ovmADDRESS;
nextMessageContext.ovmADDRESS = _address;
nextMessageContext.isCreation = false;
bool isStaticEntrypoint = false;
return _callContract(
......@@ -495,7 +546,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
nextMessageContext.ovmCALLER = nextMessageContext.ovmADDRESS;
nextMessageContext.ovmADDRESS = _address;
nextMessageContext.isStatic = true;
nextMessageContext.isCreation = false;
bool isStaticEntrypoint = true;
return _callContract(
......@@ -530,7 +580,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
{
// DELEGATECALL does not change anything about the message context.
MessageContext memory nextMessageContext = messageContext;
nextMessageContext.isCreation = false;
bool isStaticEntrypoint = false;
return _callContract(
......@@ -716,19 +765,12 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// We always need to initialize the contract with the default account values.
_initPendingAccount(_address);
// We're going into a contract creation, so we need to set this flag to get the correct
// revert behavior.
messageContext.isCreation = true;
// Actually deploy the contract and retrieve its address. This step is hiding a lot of
// complexity because we need to ensure that contract creation *never* reverts by itself.
// We cover this partially by storing a revert flag and returning (instead of reverting)
// when we know that we're inside a contract's creation code.
address ethAddress = Lib_EthUtils.createContract(_bytecode);
// Now reset this flag so we go back to normal revert behavior.
messageContext.isCreation = false;
// Contract creation returns the zero address when it fails, which should only be possible
// if the user intentionally runs out of gas. However, we might still have a bit of gas
// left over since contract calls can only be passed 63/64ths of total gas, so we need to
......@@ -781,7 +823,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
)
{
// We always update the nonce of the creating account, even if the creation fails.
_setAccountNonce(ovmADDRESS(), 1);
_setAccountNonce(ovmADDRESS(), _getAccountNonce(ovmADDRESS()) + 1);
// We're stepping into a CREATE or CREATE2, so we need to update ADDRESS to point
// to the contract's associated address and CALLER to point to the previous ADDRESS.
......@@ -834,10 +876,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
bytes memory _returndata
)
{
// EVM precompiles have the same address on L1 and L2 --> no trie lookup neededgit s.
address codeContractAddress =
uint(_contract) < 100
? _contract
: _getAccountEthAddress(_contract);
return _handleExternalInteraction(
_nextMessageContext,
_gasLimit,
_getAccountEthAddress(_contract),
codeContractAddress,
_calldata,
_isStaticEntrypoint
);
......@@ -1146,7 +1194,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// on the size of the contract code.
if (_wasAccountAlreadyLoaded == false) {
_useNuisanceGas(
Lib_EthUtils.getCodeSize(_getAccountEthAddress(_address)) * NUISANCE_GAS_PER_CONTRACT_BYTE
(Lib_EthUtils.getCodeSize(_getAccountEthAddress(_address)) * NUISANCE_GAS_PER_CONTRACT_BYTE) + MIN_NUISANCE_GAS_PER_CONTRACT
);
}
}
......@@ -1172,7 +1220,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
if (_wasAccountAlreadyChanged == false) {
ovmStateManager.incrementTotalUncommittedAccounts();
_useNuisanceGas(
Lib_EthUtils.getCodeSize(_getAccountEthAddress(_address)) * NUISANCE_GAS_PER_CONTRACT_BYTE
(Lib_EthUtils.getCodeSize(_getAccountEthAddress(_address)) * NUISANCE_GAS_PER_CONTRACT_BYTE) + MIN_NUISANCE_GAS_PER_CONTRACT
);
}
}
......@@ -1275,8 +1323,11 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
bytes memory _revertdata
)
{
// Running out of gas will return no data, so simulating it shouldn't either.
if (_flag == RevertFlag.OUT_OF_GAS) {
// Out of gas and create exceptions will fundamentally return no data, so simulating it shouldn't either.
if (
_flag == RevertFlag.OUT_OF_GAS
|| _flag == RevertFlag.CREATE_EXCEPTION
) {
return bytes('');
}
......@@ -1348,7 +1399,12 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// *single* byte, something the OVM_ExecutionManager will not return in any other case.
// We're thereby allowed to communicate failure without allowing contracts to trick us into
// thinking there was a failure.
if (messageContext.isCreation) {
bool isCreation;
assembly {
isCreation := eq(extcodesize(caller()), 0)
}
if (isCreation) {
messageRecord.revertFlag = _flag;
assembly {
......@@ -1472,24 +1528,26 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
*/
function _isValidGasLimit(
uint256 _gasLimit,
uint256 _queueOrigin
Lib_OVMCodec.QueueOrigin _queueOrigin
)
internal
returns (
bool _valid
)
{
// Always have to be below the maximum gas limit.
if (_gasLimit > gasMeterConfig.maxTransactionGasLimit) {
return false;
}
// Always have to be above the minumum gas limit.
if (_gasLimit < gasMeterConfig.minTransactionGasLimit) {
return false;
}
GasMetadataKey cumulativeGasKey;
GasMetadataKey prevEpochGasKey;
if (_queueOrigin == uint256(QueueOrigin.SEQUENCER_QUEUE)) {
if (_queueOrigin == Lib_OVMCodec.QueueOrigin.SEQUENCER_QUEUE) {
cumulativeGasKey = GasMetadataKey.CUMULATIVE_SEQUENCER_QUEUE_GAS;
prevEpochGasKey = GasMetadataKey.PREV_EPOCH_SEQUENCER_QUEUE_GAS;
} else {
......@@ -1502,7 +1560,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
_getGasMetadata(cumulativeGasKey)
- _getGasMetadata(prevEpochGasKey)
+ _gasLimit
) > gasMeterConfig.maxGasPerQueuePerEpoch
) < gasMeterConfig.maxGasPerQueuePerEpoch
);
}
......@@ -1513,12 +1571,12 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
*/
function _updateCumulativeGas(
uint256 _gasUsed,
uint256 _queueOrigin
Lib_OVMCodec.QueueOrigin _queueOrigin
)
internal
{
GasMetadataKey cumulativeGasKey;
if (_queueOrigin == uint256(QueueOrigin.SEQUENCER_QUEUE)) {
if (_queueOrigin == Lib_OVMCodec.QueueOrigin.SEQUENCER_QUEUE) {
cumulativeGasKey = GasMetadataKey.CUMULATIVE_SEQUENCER_QUEUE_GAS;
} else {
cumulativeGasKey = GasMetadataKey.CUMULATIVE_L1TOL2_QUEUE_GAS;
......@@ -1602,11 +1660,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
if (_prevMessageContext.isStatic != _nextMessageContext.isStatic) {
messageContext.isStatic = _nextMessageContext.isStatic;
}
// Avoid unnecessary the SSTORE.
if (_prevMessageContext.isCreation != _nextMessageContext.isCreation) {
messageContext.isCreation = _nextMessageContext.isCreation;
}
}
/**
......@@ -1619,9 +1672,13 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
internal
{
transactionContext.ovmTIMESTAMP = _transaction.timestamp;
transactionContext.ovmNUMBER = _transaction.number;
transactionContext.ovmTXGASLIMIT = _transaction.gasLimit;
transactionContext.ovmQUEUEORIGIN = _transaction.queueOrigin;
transactionContext.ovmL1QUEUEORIGIN = _transaction.l1QueueOrigin;
transactionContext.ovmL1TXORIGIN = _transaction.l1Txorigin;
transactionContext.ovmGASLIMIT = gasMeterConfig.maxGasPerQueuePerEpoch;
messageRecord.nuisanceGasLeft = _getNuisanceGasLimit(_transaction.gasLimit);
}
/**
......@@ -1630,11 +1687,12 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
function _resetContext()
internal
{
transactionContext.ovmORIGIN = address(0);
transactionContext.ovmL1TXORIGIN = address(0);
transactionContext.ovmTIMESTAMP = 0;
transactionContext.ovmNUMBER = 0;
transactionContext.ovmGASLIMIT = 0;
transactionContext.ovmTXGASLIMIT = 0;
transactionContext.ovmQUEUEORIGIN = 0;
transactionContext.ovmL1QUEUEORIGIN = Lib_OVMCodec.QueueOrigin.SEQUENCER_QUEUE;
transactionRecord.ovmGasRefund = 0;
......
......@@ -12,22 +12,124 @@ contract OVM_SafetyChecker is iOVM_SafetyChecker {
/********************
* Public Functions *
********************/
/**
* Checks that a given bytecode string is considered safe.
* @param _bytecode Bytecode string to check.
* @return _safe Whether or not the bytecode is safe.
* Returns whether or not all of the provided bytecode is safe.
* @param _bytecode The bytecode to safety check.
* @return `true` if the bytecode is safe, `false` otherwise.
*/
function isBytecodeSafe(
bytes memory _bytecode
)
override
public
external
view
returns (
bool _safe
)
returns (bool)
{
// autogenerated by gen_safety_checker_constants.py
// number of bytes to skip for each opcode
uint256[8] memory opcodeSkippableBytes = [
uint256(0x0001010101010101010101010000000001010101010101010101010101010000),
uint256(0x0100000000000000000000000000000000000000010101010101000000010100),
uint256(0x0000000000000000000000000000000001010101000000010101010100000000),
uint256(0x0203040500000000000000000000000000000000000000000000000000000000),
uint256(0x0101010101010101010101010101010101010101010101010101010101010101),
uint256(0x0101010101000000000000000000000000000000000000000000000000000000),
uint256(0x0000000000000000000000000000000000000000000000000000000000000000),
uint256(0x0000000000000000000000000000000000000000000000000000000000000000)
];
// Mask to gate opcode specific cases
uint256 opcodeGateMask = ~uint256(0xffffffffffffffffffffffe000000000fffffffff070ffff9c0ffffec000f001);
// Halting opcodes
uint256 opcodeHaltingMask = ~uint256(0x4008000000000000000000000000000000000000004000000000000000000001);
// PUSH opcodes
uint256 opcodePushMask = ~uint256(0xffffffff000000000000000000000000);
uint256 codeLength;
uint256 _pc;
assembly {
_pc := add(_bytecode, 0x20)
}
codeLength = _pc + _bytecode.length;
do {
// current opcode: 0x00...0xff
uint256 opNum;
// inline assembly removes the extra add + bounds check
assembly {
let word := mload(_pc) //load the next 32 bytes at pc into word
// Look up number of bytes to skip from opcodeSkippableBytes and then update indexInWord
// E.g. the 02030405 in opcodeSkippableBytes is the number of bytes to skip for PUSH1->4
// We repeat this 6 times, thus we can only skip bytes for up to PUSH4 ((1+4) * 6 = 30 < 32).
// If we see an opcode that is listed as 0 skippable bytes e.g. PUSH5,
// then we will get stuck on that indexInWord and then opNum will be set to the PUSH5 opcode.
let indexInWord := byte(0, mload(add(opcodeSkippableBytes, byte(0, word))))
indexInWord := add(indexInWord, byte(0, mload(add(opcodeSkippableBytes, byte(indexInWord, word)))))
indexInWord := add(indexInWord, byte(0, mload(add(opcodeSkippableBytes, byte(indexInWord, word)))))
indexInWord := add(indexInWord, byte(0, mload(add(opcodeSkippableBytes, byte(indexInWord, word)))))
indexInWord := add(indexInWord, byte(0, mload(add(opcodeSkippableBytes, byte(indexInWord, word)))))
indexInWord := add(indexInWord, byte(0, mload(add(opcodeSkippableBytes, byte(indexInWord, word)))))
_pc := add(_pc, indexInWord)
opNum := byte(indexInWord, word)
}
// + push opcodes
// + stop opcodes [STOP(0x00),JUMP(0x56),RETURN(0xf3),INVALID(0xfe)]
// + caller opcode CALLER(0x33)
// + blacklisted opcodes
uint256 opBit = 1 << opNum;
if (opBit & opcodeGateMask == 0) {
if (opBit & opcodePushMask == 0) {
// all pushes are valid opcodes
// subsequent bytes are not opcodes. Skip them.
_pc += (opNum - 0x5e); // PUSH1 is 0x60, so opNum-0x5f = PUSHed bytes and we +1 to
// skip the _pc++; line below in order to save gas ((-0x5f + 1) = -0x5e)
continue;
} else if (opBit & opcodeHaltingMask == 0) {
// STOP or JUMP or RETURN or INVALID (Note: REVERT is blacklisted, so not included here)
// We are now inside unreachable code until we hit a JUMPDEST!
do {
_pc++;
assembly {
opNum := byte(0, mload(_pc))
}
// encountered a JUMPDEST
if (opNum == 0x5b) break;
// skip PUSHed bytes
if ((1 << opNum) & opcodePushMask == 0) _pc += (opNum - 0x5f); // opNum-0x5f = PUSHed bytes (PUSH1 is 0x60)
} while (_pc < codeLength);
// opNum is 0x5b, so we don't continue here since the pc++ is fine
} else if (opNum == 0x33) { // Caller opcode
uint256 firstOps; // next 32 bytes of bytecode
uint256 secondOps; // following 32 bytes of bytecode
assembly {
firstOps := mload(_pc)
// 32 - 4 bytes = 28 bytes = 224 bits
secondOps := shr(224, mload(add(_pc, 0x20)))
}
// Call identity precompile
// CALLER POP PUSH1 0x00 PUSH1 0x04 GAS CALL
// 32 - 8 bytes = 24 bytes = 192
if ((firstOps >> 192) == 0x3350600060045af1) {
_pc += 8;
// Call EM and abort execution if instructed
// CALLER PUSH1 0x00 SWAP1 GAS CALL PC PUSH1 0x1d ADD EQ JUMPI RETURNDATASIZE PUSH1 0x00 DUP1 RETURNDATACOPY PUSH1 0x00 REVERT JUMPDEST PUSH1 0x01 PUSH1 0x00 RETURN JUMPDEST
} else if (firstOps == 0x336000905af158601d0157586012013d600114573d6000803e3d6000fd5b6001 && secondOps == 0x6000f35b) {
_pc += 36;
} else {
return false;
}
continue;
} else {
// encountered a non-whitelisted opcode!
return false;
}
}
_pc++;
} while (_pc < codeLength);
return true;
}
}
......@@ -25,8 +25,8 @@ contract OVM_StateManager is iOVM_StateManager {
* Contract Variables: Contract References *
*******************************************/
address internal owner;
address internal ovmExecutionManager;
address override public owner;
address override public ovmExecutionManager;
/****************************************
......@@ -111,6 +111,20 @@ contract OVM_StateManager is iOVM_StateManager {
accounts[_address] = _account;
}
/**
* Marks an account as empty.
* @param _address Address of the account to mark.
*/
function putEmptyAccount(
address _address
)
override
public
authenticated
{
accounts[_address].codeHash = EMPTY_ACCOUNT_CODE_HASH;
}
/**
* Retrieves an account from the state.
* @param _address Address of the account to retrieve.
......@@ -215,6 +229,24 @@ contract OVM_StateManager is iOVM_StateManager {
return accounts[_address].ethAddress;
}
/**
* Retrieves the storage root of an account.
* @param _address Address of the account to access.
* @return _storageRoot Corresponding storage root.
*/
function getAccountStorageRoot(
address _address
)
override
public
view
returns (
bytes32 _storageRoot
)
{
return accounts[_address].storageRoot;
}
/**
* Initializes a pending account (during CREATE or CREATE2) with the default values.
* @param _address Address of the account to initialize.
......@@ -228,7 +260,7 @@ contract OVM_StateManager is iOVM_StateManager {
{
Lib_OVMCodec.Account storage account = accounts[_address];
account.nonce = 1;
account.codeHash = keccak256(hex'80');
account.codeHash = keccak256(hex'');
account.isFresh = true;
}
......@@ -376,10 +408,7 @@ contract OVM_StateManager is iOVM_StateManager {
// storage because writing to zero when the actual value is nonzero causes a gas
// discrepancy. Could be moved into a new `putVerifiedContractStorage` function, or
// something along those lines.
if (
verifiedContractStorage[_contract][_key] == false
&& accounts[_contract].isFresh == false
) {
if (verifiedContractStorage[_contract][_key] == false) {
verifiedContractStorage[_contract][_key] = true;
}
}
......@@ -401,6 +430,15 @@ contract OVM_StateManager is iOVM_StateManager {
bytes32 _value
)
{
// Storage XOR system doesn't work for newly created contracts that haven't set this
// storage slot value yet.
if (
verifiedContractStorage[_contract][_key] == false
&& accounts[_contract].isFresh
) {
return bytes32(0);
}
// See `putContractStorage` for more information about the XOR here.
return contractStorage[_contract][_key] ^ STORAGE_XOR_VALUE;
}
......
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_Bytes32Utils } from "../../libraries/utils/Lib_Bytes32Utils.sol";
/* Interface Imports */
import { iOVM_DeployerWhitelist } from "../../iOVM/precompiles/iOVM_DeployerWhitelist.sol";
import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManager.sol";
/**
* @title OVM_DeployerWhitelist
*/
contract OVM_DeployerWhitelist is iOVM_DeployerWhitelist {
/**********************
* Contract Constants *
**********************/
bytes32 internal constant KEY_INITIALIZED = 0x0000000000000000000000000000000000000000000000000000000000000010;
bytes32 internal constant KEY_OWNER = 0x0000000000000000000000000000000000000000000000000000000000000011;
bytes32 internal constant KEY_ALLOW_ARBITRARY_DEPLOYMENT = 0x0000000000000000000000000000000000000000000000000000000000000012;
/**********************
* Function Modifiers *
**********************/
/**
* Blocks functions to anyone except the contract owner.
*/
modifier onlyOwner() {
iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(msg.sender);
address owner = Lib_Bytes32Utils.toAddress(
ovmExecutionManager.ovmSLOAD(
KEY_OWNER
)
);
require(
ovmExecutionManager.ovmCALLER() == owner,
"Function can only be called by the owner of this contract."
);
_;
}
/********************
* Public Functions *
********************/
/**
* Initializes the whitelist.
* @param _owner Address of the owner for this contract.
* @param _allowArbitraryDeployment Whether or not to allow arbitrary contract deployment.
*/
function initialize(
address _owner,
bool _allowArbitraryDeployment
)
override
public
{
iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(msg.sender);
bool initialized = Lib_Bytes32Utils.toBool(
ovmExecutionManager.ovmSLOAD(KEY_INITIALIZED)
);
if (initialized == true) {
return;
}
ovmExecutionManager.ovmSSTORE(
KEY_INITIALIZED,
Lib_Bytes32Utils.fromBool(true)
);
ovmExecutionManager.ovmSSTORE(
KEY_OWNER,
Lib_Bytes32Utils.fromAddress(_owner)
);
ovmExecutionManager.ovmSSTORE(
KEY_ALLOW_ARBITRARY_DEPLOYMENT,
Lib_Bytes32Utils.fromBool(_allowArbitraryDeployment)
);
}
/**
* Adds or removes an address from the deployment whitelist.
* @param _deployer Address to update permissions for.
* @param _isWhitelisted Whether or not the address is whitelisted.
*/
function setWhitelistedDeployer(
address _deployer,
bool _isWhitelisted
)
override
public
onlyOwner
{
iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(msg.sender);
ovmExecutionManager.ovmSSTORE(
Lib_Bytes32Utils.fromAddress(_deployer),
Lib_Bytes32Utils.fromBool(_isWhitelisted)
);
}
/**
* Updates the owner of this contract.
* @param _owner Address of the new owner.
*/
function setOwner(
address _owner
)
override
public
onlyOwner
{
iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(msg.sender);
ovmExecutionManager.ovmSSTORE(
KEY_OWNER,
Lib_Bytes32Utils.fromAddress(_owner)
);
}
/**
* Updates the arbitrary deployment flag.
* @param _allowArbitraryDeployment Whether or not to allow arbitrary contract deployment.
*/
function setAllowArbitraryDeployment(
bool _allowArbitraryDeployment
)
override
public
onlyOwner
{
iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(msg.sender);
ovmExecutionManager.ovmSSTORE(
KEY_ALLOW_ARBITRARY_DEPLOYMENT,
Lib_Bytes32Utils.fromBool(_allowArbitraryDeployment)
);
}
/**
* Permanently enables arbitrary contract deployment and deletes the owner.
*/
function enableArbitraryContractDeployment()
override
public
onlyOwner
{
setAllowArbitraryDeployment(true);
setOwner(address(0));
}
/**
* Checks whether an address is allowed to deploy contracts.
* @param _deployer Address to check.
* @return _allowed Whether or not the address can deploy contracts.
*/
function isDeployerAllowed(
address _deployer
)
override
public
returns (
bool _allowed
)
{
iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(msg.sender);
bool initialized = Lib_Bytes32Utils.toBool(
ovmExecutionManager.ovmSLOAD(KEY_INITIALIZED)
);
if (initialized == false) {
return true;
}
bool allowArbitraryDeployment = Lib_Bytes32Utils.toBool(
ovmExecutionManager.ovmSLOAD(KEY_ALLOW_ARBITRARY_DEPLOYMENT)
);
if (allowArbitraryDeployment == true) {
return true;
}
bool isWhitelisted = Lib_Bytes32Utils.toBool(
ovmExecutionManager.ovmSLOAD(
Lib_Bytes32Utils.fromAddress(_deployer)
)
);
return isWhitelisted;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Interface Imports */
import { iOVM_L1MessageSender } from "../../iOVM/precompiles/iOVM_L1MessageSender.sol";
import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManager.sol";
/**
* @title OVM_L1MessageSender
*/
contract OVM_L1MessageSender is iOVM_L1MessageSender {
/********************
* Public Functions *
********************/
/**
* @return _l1MessageSender L1 message sender address (msg.sender).
*/
function getL1MessageSender()
override
public
returns (
address _l1MessageSender
)
{
return iOVM_ExecutionManager(msg.sender).ovmL1TXORIGIN();
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Interface Imports */
import { iOVM_L2ToL1MessagePasser } from "../../iOVM/precompiles/iOVM_L2ToL1MessagePasser.sol";
import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManager.sol";
/**
* @title OVM_L2ToL1MessagePasser
*/
contract OVM_L2ToL1MessagePasser is iOVM_L2ToL1MessagePasser {
/**********************
* Contract Variables *
**********************/
uint256 internal nonce;
/********************
* Public Functions *
********************/
/**
* Passes a message to L1.
* @param _message Message to pass to L1.
*/
function passMessageToL1(
bytes memory _message
)
override
public
{
// For now, to be trustfully relayed by sequencer to L1, so just emit
// an event for the sequencer to pick up.
emit L2ToL1Message(
nonce,
iOVM_ExecutionManager(msg.sender).ovmCALLER(),
_message
);
nonce = nonce + 1;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
import { iOVM_BaseQueue } from "../../iOVM/queue/iOVM_BaseQueue.sol";
/**
* @title OVM_BaseQueue
*/
contract OVM_BaseQueue is iOVM_BaseQueue {
/****************************************
* Contract Variables: Internal Storage *
****************************************/
Lib_OVMCodec.QueueElement[] internal queue;
uint256 internal front;
/**********************
* Function Modifiers *
**********************/
/**
* Asserts that the queue is not empty.
*/
modifier notEmpty() {
require(
size() > 0,
"Queue is empty."
);
_;
}
/**********************************
* Public Functions: Queue Access *
**********************************/
/**
* Gets the size of the queue.
* @return _size Number of elements in the queue.
*/
function size()
override
public
view
returns (
uint256 _size
)
{
return front >= queue.length ? 0 : queue.length - front;
}
/**
* Gets the top element of the queue.
* @return _element First element in the queue.
*/
function peek()
override
public
view
notEmpty
returns (
Lib_OVMCodec.QueueElement memory _element
)
{
return queue[front];
}
/******************************************
* Internal Functions: Queue Manipulation *
******************************************/
/**
* Adds an element to the queue.
* @param _element Queue element to add to the queue.
*/
function _enqueue(
Lib_OVMCodec.QueueElement memory _element
)
internal
{
queue.push(_element);
}
/**
* Pops an element from the queue.
* @return _element Queue element popped from the queue.
*/
function _dequeue()
internal
notEmpty
returns (
Lib_OVMCodec.QueueElement memory _element
)
{
_element = queue[front];
delete queue[front];
front += 1;
return _element;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Proxy Imports */
import { Proxy_Resolver } from "../../proxy/Proxy_Resolver.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
import { iOVM_L1ToL2TransactionQueue } from "../../iOVM/queue/iOVM_L1ToL2TransactionQueue.sol";
/* Contract Imports */
import { OVM_BaseQueue } from "./OVM_BaseQueue.sol";
/**
* @title OVM_L1ToL2TransactionQueue
*/
contract OVM_L1ToL2TransactionQueue is iOVM_L1ToL2TransactionQueue, OVM_BaseQueue, Proxy_Resolver {
/*******************************************
* Contract Variables: Contract References *
*******************************************/
address internal ovmCanonicalTransactionChain;
/***************
* Constructor *
***************/
/**
* @param _proxyManager Address of the Proxy_Manager.
*/
constructor(
address _proxyManager
)
Proxy_Resolver(_proxyManager)
{
ovmCanonicalTransactionChain = resolve("OVM_CanonicalTransactionChain");
}
/****************************************
* Public Functions: Queue Manipulation *
****************************************/
/**
* Adds an element to the queue.
* @param _element Queue element to add to the queue.
*/
function enqueue(
Lib_OVMCodec.QueueElement memory _element
)
override
public
{
_enqueue(_element);
}
/**
* Pops an element from the queue.
* @return _element Queue element popped from the queue.
*/
function dequeue()
override
public
returns (
Lib_OVMCodec.QueueElement memory _element
)
{
require(
msg.sender == ovmCanonicalTransactionChain,
"Sender is not allowed to enqueue."
);
return _dequeue();
}
}
......@@ -2,11 +2,9 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Proxy Imports */
import { Proxy_Resolver } from "../../proxy/Proxy_Resolver.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
/* Interface Imports */
import { iOVM_FraudVerifier } from "../../iOVM/verification/iOVM_FraudVerifier.sol";
......@@ -17,7 +15,7 @@ import { iOVM_StateManagerFactory } from "../../iOVM/execution/iOVM_StateManager
import { iOVM_StateCommitmentChain } from "../../iOVM/chain/iOVM_StateCommitmentChain.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
contract OVM_FraudVerifier is iOVM_FraudVerifier, Proxy_Resolver {
contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
/*******************************************
* Contract Variables: Contract References *
......@@ -39,12 +37,12 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Proxy_Resolver {
***************/
/**
* @param _proxyManager Address of the Proxy_Manager.
* @param _libAddressManager Address of the Address Manager.
*/
constructor(
address _proxyManager
address _libAddressManager
)
Proxy_Resolver(_proxyManager)
Lib_AddressResolver(_libAddressManager)
{
ovmStateCommitmentChain = iOVM_StateCommitmentChain(resolve("OVM_StateCommitmentChain"));
ovmCanonicalTransactionChain = iOVM_CanonicalTransactionChain(resolve("OVM_CanonicalTransactionChain"));
......@@ -123,7 +121,7 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Proxy_Resolver {
transitioners[_preStateRoot] = iOVM_StateTransitionerFactory(
resolve("OVM_StateTransitionerFactory")
).create(
address(proxyManager),
address(libAddressManager),
_preStateRootProof.index,
_preStateRoot,
Lib_OVMCodec.hashTransaction(_transaction)
......
......@@ -2,13 +2,11 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Proxy Imports */
import { Proxy_Resolver } from "../../proxy/Proxy_Resolver.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_EthUtils } from "../../libraries/utils/Lib_EthUtils.sol";
import { Lib_EthMerkleTrie } from "../../libraries/trie/Lib_EthMerkleTrie.sol";
import { Lib_SecureMerkleTrie } from "../../libraries/trie/Lib_SecureMerkleTrie.sol";
/* Interface Imports */
import { iOVM_StateTransitioner } from "../../iOVM/verification/iOVM_StateTransitioner.sol";
......@@ -16,10 +14,13 @@ import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManage
import { iOVM_StateManager } from "../../iOVM/execution/iOVM_StateManager.sol";
import { iOVM_StateManagerFactory } from "../../iOVM/execution/iOVM_StateManagerFactory.sol";
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
/**
* @title OVM_StateTransitioner
*/
contract OVM_StateTransitioner is iOVM_StateTransitioner, Proxy_Resolver {
contract OVM_StateTransitioner is iOVM_StateTransitioner, Lib_AddressResolver {
/*******************
* Data Structures *
......@@ -56,18 +57,18 @@ contract OVM_StateTransitioner is iOVM_StateTransitioner, Proxy_Resolver {
***************/
/**
* @param _proxyManager Address of the Proxy_Manager.
* @param _libAddressManager Address of the Address Manager.
* @param _stateTransitionIndex Index of the state transition being verified.
* @param _preStateRoot State root before the transition was executed.
* @param _transactionHash Hash of the executed transaction.
*/
constructor(
address _proxyManager,
address _libAddressManager,
uint256 _stateTransitionIndex,
bytes32 _preStateRoot,
bytes32 _transactionHash
)
Proxy_Resolver(_proxyManager)
Lib_AddressResolver(_libAddressManager)
{
stateTransitionIndex = _stateTransitionIndex;
preStateRoot = _preStateRoot;
......@@ -160,70 +161,128 @@ contract OVM_StateTransitioner is iOVM_StateTransitioner, Proxy_Resolver {
*/
function proveContractState(
address _ovmContractAddress,
Lib_OVMCodec.Account memory _account,
address _ethContractAddress,
Lib_OVMCodec.EVMAccount memory _account,
bytes memory _stateTrieWitness
)
override
public
onlyDuringPhase(TransitionPhase.PRE_EXECUTION)
{
// Exit quickly to avoid unnecessary work.
require(
_account.codeHash == Lib_EthUtils.getCodeHash(_account.ethAddress),
ovmStateManager.hasAccount(_ovmContractAddress) == false,
"Account state has already been proven"
);
require(
_account.codeHash == Lib_EthUtils.getCodeHash(_ethContractAddress),
"Invalid code hash provided."
);
require(
Lib_EthMerkleTrie.proveAccountState(
_ovmContractAddress,
_account,
Lib_SecureMerkleTrie.verifyInclusionProof(
abi.encodePacked(_ovmContractAddress),
Lib_OVMCodec.encodeEVMAccount(_account),
_stateTrieWitness,
preStateRoot
),
"Invalid account state provided."
"Account state is not correct or invalid inclusion proof provided."
);
ovmStateManager.putAccount(
_ovmContractAddress,
_account
Lib_OVMCodec.Account({
nonce: _account.nonce,
balance: _account.balance,
storageRoot: _account.storageRoot,
codeHash: _account.codeHash,
ethAddress: _ethContractAddress,
isFresh: false
})
);
}
/**
* Allows a user to prove that an account does *not* exist in the state.
* @param _ovmContractAddress Address of the contract on the OVM.
* @param _stateTrieWitness Proof of the (empty) account state.
*/
function proveEmptyContractState(
address _ovmContractAddress,
bytes memory _stateTrieWitness
)
override
public
onlyDuringPhase(TransitionPhase.PRE_EXECUTION)
{
// Exit quickly to avoid unnecessary work.
require(
ovmStateManager.hasEmptyAccount(_ovmContractAddress) == false,
"Account state has already been proven."
);
require(
Lib_SecureMerkleTrie.verifyExclusionProof(
abi.encodePacked(_ovmContractAddress),
_stateTrieWitness,
preStateRoot
),
"Account is not empty or invalid inclusion proof provided."
);
ovmStateManager.putEmptyAccount(_ovmContractAddress);
}
/**
* Allows a user to prove the initial state of a contract storage slot.
* @param _ovmContractAddress Address of the contract on the OVM.
* @param _key Claimed account slot key.
* @param _value Claimed account slot value.
* @param _stateTrieWitness Proof of the account state.
* @param _storageTrieWitness Proof of the storage slot.
*/
function proveStorageSlot(
address _ovmContractAddress,
bytes32 _key,
bytes32 _value,
bytes memory _stateTrieWitness,
bytes memory _storageTrieWitness
)
override
public
onlyDuringPhase(TransitionPhase.PRE_EXECUTION)
{
// Exit quickly to avoid unnecessary work.
require(
ovmStateManager.hasContractStorage(_ovmContractAddress, _key) == false,
"Storage slot has already been proven."
);
require(
ovmStateManager.hasAccount(_ovmContractAddress) == true,
"Contract must be verified before proving a storage slot."
);
require(
Lib_EthMerkleTrie.proveAccountStorageSlotValue(
_ovmContractAddress,
_key,
_value,
_stateTrieWitness,
(
bool exists,
bytes memory value
) = Lib_SecureMerkleTrie.get(
abi.encodePacked(_key),
_storageTrieWitness,
preStateRoot
),
"Invalid account state provided."
ovmStateManager.getAccountStorageRoot(_ovmContractAddress)
);
if (exists == true) {
require(
keccak256(value) == keccak256(abi.encodePacked(_value)),
"Provided storage slot value is invalid."
);
} else {
require(
_value == bytes32(0),
"Provided storage slot value is invalid."
);
}
ovmStateManager.putContractStorage(
_ovmContractAddress,
_key,
......@@ -245,15 +304,21 @@ contract OVM_StateTransitioner is iOVM_StateTransitioner, Proxy_Resolver {
)
override
public
onlyDuringPhase(TransitionPhase.PRE_EXECUTION)
{
require(
Lib_OVMCodec.hashTransaction(_transaction) == transactionHash,
"Invalid transaction provided."
);
// TODO: Set state manager for EM here.
// We call `setExecutionManager` right before `run` (and not earlier) just in case the
// OVM_ExecutionManager address was updated between the time when this contract was created
// and when `applyTransaction` was called.
ovmStateManager.setExecutionManager(resolve("OVM_ExecutionManager"));
ovmStateManager.setExecutionManager(resolveTarget("OVM_ExecutionManager"));
// `run` always succeeds *unless* the user hasn't provided enough gas to `applyTransaction`
// or an INVALID_STATE_ACCESS flag was triggered. Either way, we won't get beyond this line
// if that's the case.
ovmExecutionManager.run(_transaction, address(ovmStateManager));
phase = TransitionPhase.POST_EXECUTION;
......@@ -272,7 +337,7 @@ contract OVM_StateTransitioner is iOVM_StateTransitioner, Proxy_Resolver {
*/
function commitContractState(
address _ovmContractAddress,
Lib_OVMCodec.Account memory _account,
Lib_OVMCodec.EVMAccount memory _account,
bytes memory _stateTrieWitness
)
override
......@@ -281,12 +346,12 @@ contract OVM_StateTransitioner is iOVM_StateTransitioner, Proxy_Resolver {
{
require(
ovmStateManager.commitAccount(_ovmContractAddress) == true,
"Cannot commit an account that has not been changed."
"Account was not changed or has already been committed."
);
postStateRoot = Lib_EthMerkleTrie.updateAccountState(
_ovmContractAddress,
_account,
postStateRoot = Lib_SecureMerkleTrie.update(
abi.encodePacked(_ovmContractAddress),
Lib_OVMCodec.encodeEVMAccount(_account),
_stateTrieWitness,
postStateRoot
);
......@@ -313,15 +378,24 @@ contract OVM_StateTransitioner is iOVM_StateTransitioner, Proxy_Resolver {
{
require(
ovmStateManager.commitContractStorage(_ovmContractAddress, _key) == true,
"Cannot commit a storage slot that has not been changed."
"Storage slot was not changed or has already been committed."
);
postStateRoot = Lib_EthMerkleTrie.updateAccountStorageSlotValue(
_ovmContractAddress,
_key,
_value,
_stateTrieWitness,
Lib_OVMCodec.EVMAccount memory account = Lib_OVMCodec.toEVMAccount(
ovmStateManager.getAccount(_ovmContractAddress)
);
account.storageRoot = Lib_SecureMerkleTrie.update(
abi.encodePacked(_key),
abi.encodePacked(_value),
_storageTrieWitness,
account.storageRoot
);
postStateRoot = Lib_SecureMerkleTrie.update(
abi.encodePacked(_ovmContractAddress),
Lib_OVMCodec.encodeEVMAccount(account),
_stateTrieWitness,
postStateRoot
);
}
......
......@@ -19,14 +19,14 @@ contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory {
/**
* Creates a new OVM_StateTransitioner
* @param _proxyManager Address of the Proxy_Manager.
* @param _libAddressManager Address of the Address Manager.
* @param _stateTransitionIndex Index of the state transition being verified.
* @param _preStateRoot State root before the transition was executed.
* @param _transactionHash Hash of the executed transaction.
* @return _ovmStateTransitioner New OVM_StateTransitioner instance.
*/
function create(
address _proxyManager,
address _libAddressManager,
uint256 _stateTransitionIndex,
bytes32 _preStateRoot,
bytes32 _transactionHash
......@@ -38,7 +38,7 @@ contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory {
)
{
return new OVM_StateTransitioner(
_proxyManager,
_libAddressManager,
_stateTransitionIndex,
_preStateRoot,
_transactionHash
......
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title iOVM_BaseCrossDomainMessenger
*/
interface iOVM_BaseCrossDomainMessenger {
/********************
* Public Functions *
********************/
/**
* Sends a cross domain message to the target messenger.
* @param _target Target contract address.
* @param _message Message to send to the target.
* @param _gasLimit Gas limit for the provided message.
*/
function sendMessage(
address _target,
bytes memory _message,
uint256 _gasLimit
) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
import { iOVM_BaseCrossDomainMessenger } from "./iOVM_BaseCrossDomainMessenger.sol";
/**
* @title iOVM_L1CrossDomainMessenger
*/
interface iOVM_L1CrossDomainMessenger is iOVM_BaseCrossDomainMessenger {
/*******************
* Data Structures *
*******************/
struct L2MessageInclusionProof {
bytes32 stateRoot;
Lib_OVMCodec.ChainBatchHeader stateRootBatchHeader;
Lib_OVMCodec.ChainInclusionProof stateRootProof;
bytes stateTrieWitness;
bytes storageTrieWitness;
}
/********************
* Public Functions *
********************/
/**
* Relays a cross domain message to a contract.
* @param _target Target contract address.
* @param _sender Message sender address.
* @param _message Message to send to the target.
* @param _messageNonce Nonce for the provided message.
* @param _proof Inclusion proof for the given message.
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
L2MessageInclusionProof memory _proof
) external;
/**
* Replays a cross domain message to the target messenger.
* @param _target Target contract address.
* @param _sender Original sender address.
* @param _message Message to send to the target.
* @param _messageNonce Nonce for the provided message.
* @param _gasLimit Gas limit for the provided message.
*/
function replayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
uint32 _gasLimit
) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_BaseCrossDomainMessenger } from "./iOVM_BaseCrossDomainMessenger.sol";
/**
* @title iOVM_L2CrossDomainMessenger
*/
interface iOVM_L2CrossDomainMessenger is iOVM_BaseCrossDomainMessenger {
/********************
* Public Functions *
********************/
/**
* Relays a cross domain message to a contract.
* @param _target Target contract address.
* @param _sender Message sender address.
* @param _message Message to send to the target.
* @param _messageNonce Nonce for the provided message.
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
) external;
}
......@@ -2,6 +2,9 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
......@@ -10,10 +13,90 @@ import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
*/
interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
/****************************************
* Public Functions: Batch Manipulation *
****************************************/
/**********
* Events *
**********/
event QueueTransactionAppended(
bytes _transaction,
bytes32 _timestampAndBlockNumber
);
event ChainBatchAppended(
uint256 _startingQueueIndex,
uint256 _numQueueElements
);
/***********
* Structs *
***********/
struct BatchContext {
uint256 numSequencedTransactions;
uint256 numSubsequentQueueTransactions;
uint256 timestamp;
uint256 blockNumber;
}
struct TransactionChainElement {
bool isSequenced;
uint256 queueIndex; // QUEUED TX ONLY
uint256 timestamp; // SEQUENCER TX ONLY
uint256 blockNumber; // SEQUENCER TX ONLY
bytes txData; // SEQUENCER TX ONLY
}
function appendQueueBatch() external;
function appendSequencerBatch(bytes[] calldata _batch, uint256 _timestamp) external;
/********************
* Public Functions *
********************/
/**
* Gets the queue element at a particular index.
* @param _index Index of the queue element to access.
* @return _element Queue element at the given index.
*/
function getQueueElement(
uint256 _index
)
external
view
returns (
Lib_OVMCodec.QueueElement memory _element
);
/**
* Adds a transaction to the queue.
* @param _target Target contract to send the transaction to.
* @param _gasLimit Gas limit for the given transaction.
* @param _data Transaction data.
*/
function enqueue(
address _target,
uint256 _gasLimit,
bytes memory _data
) external;
/**
* Appends a given number of queued transactions as a single batch.
* @param _numQueuedTransactions Number of transactions to append.
*/
function appendQueueBatch(
uint256 _numQueuedTransactions
) external;
/**
* Allows the sequencer to append a batch of transactions.
* @param _transactions Array of raw transaction data.
* @param _contexts Array of batch contexts.
* @param _shouldStartAtBatch Specific batch we expect to start appending to.
* @param _totalElementsToAppend Total number of batch elements we expect to append.
*/
function appendSequencerBatch(
bytes[] memory _transactions,
BatchContext[] memory _contexts,
uint256 _shouldStartAtBatch,
uint _totalElementsToAppend
) external;
}
......@@ -19,4 +19,10 @@ interface iOVM_StateCommitmentChain is iOVM_BaseChain {
function appendStateBatch(bytes32[] calldata _batch) external;
function deleteStateBatch(Lib_OVMCodec.ChainBatchHeader memory _batchHeader) external;
/**********************************
* Public Functions: Batch Status *
**********************************/
function insideFraudProofWindow(Lib_OVMCodec.ChainBatchHeader memory _batchHeader) external view returns (bool _inside);
}
......@@ -30,12 +30,6 @@ interface iOVM_ExecutionManager {
PREV_EPOCH_L1TOL2_QUEUE_GAS
}
enum QueueOrigin {
SEQUENCER_QUEUE,
L1TOL2_QUEUE
}
/***********
* Structs *
***********/
......@@ -52,11 +46,12 @@ interface iOVM_ExecutionManager {
}
struct TransactionContext {
address ovmORIGIN;
Lib_OVMCodec.QueueOrigin ovmL1QUEUEORIGIN;
uint256 ovmTIMESTAMP;
uint256 ovmNUMBER;
uint256 ovmGASLIMIT;
uint256 ovmTXGASLIMIT;
uint256 ovmQUEUEORIGIN;
address ovmL1TXORIGIN;
}
struct TransactionRecord {
......@@ -67,7 +62,6 @@ interface iOVM_ExecutionManager {
address ovmCALLER;
address ovmADDRESS;
bool isStatic;
bool isCreation;
}
struct MessageRecord {
......@@ -92,12 +86,20 @@ interface iOVM_ExecutionManager {
function ovmCALLER() external view returns (address _caller);
function ovmADDRESS() external view returns (address _address);
function ovmORIGIN() external view returns (address _origin);
function ovmTIMESTAMP() external view returns (uint256 _timestamp);
function ovmNUMBER() external view returns (uint256 _number);
function ovmGASLIMIT() external view returns (uint256 _gasLimit);
function ovmCHAINID() external view returns (uint256 _chainId);
/**********************
* L2 Context Opcodes *
**********************/
function ovmL1QUEUEORIGIN() external view returns (Lib_OVMCodec.QueueOrigin _queueOrigin);
function ovmL1TXORIGIN() external view returns (address _l1TxOrigin);
/*******************
* Halting Opcodes *
*******************/
......
......@@ -10,5 +10,5 @@ interface iOVM_SafetyChecker {
* Public Functions *
********************/
function isBytecodeSafe(bytes memory _bytecode) external view returns (bool _safe);
function isBytecodeSafe(bytes memory _bytecode) external view returns (bool);
}
......@@ -26,6 +26,8 @@ interface iOVM_StateManager {
* Public Functions: Setup *
***************************/
function owner() external view returns (address _owner);
function ovmExecutionManager() external view returns (address _ovmExecutionManager);
function setExecutionManager(address _ovmExecutionManager) external;
......@@ -34,12 +36,14 @@ interface iOVM_StateManager {
************************************/
function putAccount(address _address, Lib_OVMCodec.Account memory _account) external;
function putEmptyAccount(address _address) external;
function getAccount(address _address) external view returns (Lib_OVMCodec.Account memory _account);
function hasAccount(address _address) external view returns (bool _exists);
function hasEmptyAccount(address _address) external view returns (bool _exists);
function setAccountNonce(address _address, uint256 _nonce) external;
function getAccountNonce(address _address) external view returns (uint256 _nonce);
function getAccountEthAddress(address _address) external view returns (address _ethAddress);
function getAccountStorageRoot(address _address) external view returns (bytes32 _storageRoot);
function initPendingAccount(address _address) external;
function commitPendingAccount(address _address, address _ethAddress, bytes32 _codeHash) external;
function testAndSetAccountLoaded(address _address) external returns (bool _wasAccountAlreadyLoaded);
......
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/**
* @title iOVM_DeployerWhitelist
*/
interface iOVM_DeployerWhitelist {
/********************
* Public Functions *
********************/
function initialize(address _owner, bool _allowArbitraryDeployment) external;
function setWhitelistedDeployer(address _deployer, bool _isWhitelisted) external;
function setOwner(address _newOwner) external;
function setAllowArbitraryDeployment(bool _allowArbitraryDeployment) external;
function enableArbitraryContractDeployment() external;
function isDeployerAllowed(address _deployer) external returns (bool _allowed);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/**
* @title iOVM_L1MessageSender
*/
interface iOVM_L1MessageSender {
/********************
* Public Functions *
********************/
function getL1MessageSender() external returns (address _l1MessageSender);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/**
* @title iOVM_L2ToL1MessagePasser
*/
interface iOVM_L2ToL1MessagePasser {
/**********
* Events *
**********/
event L2ToL1Message(
uint _nonce,
address _sender,
bytes _data
);
/********************
* Public Functions *
********************/
function passMessageToL1(bytes memory _message) external;
}
// 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_BaseQueue
*/
interface iOVM_BaseQueue {
/**********************************
* Public Functions: Queue Access *
**********************************/
function size() external view returns (uint256 _size);
function peek() external view returns (Lib_OVMCodec.QueueElement memory _element);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
import { iOVM_BaseQueue } from "./iOVM_BaseQueue.sol";
/**
* @title iOVM_L1ToL2TransactionQueue
*/
interface iOVM_L1ToL2TransactionQueue is iOVM_BaseQueue {
/****************************************
* Public Functions: Queue Manipulation *
****************************************/
function enqueue(Lib_OVMCodec.QueueElement memory _element) external;
function dequeue() external returns (Lib_OVMCodec.QueueElement memory _element);
}
......@@ -25,7 +25,13 @@ interface iOVM_StateTransitioner {
function proveContractState(
address _ovmContractAddress,
Lib_OVMCodec.Account calldata _account,
address _ethContractAddress,
Lib_OVMCodec.EVMAccount calldata _account,
bytes calldata _stateTrieWitness
) external;
function proveEmptyContractState(
address _ovmContractAddress,
bytes calldata _stateTrieWitness
) external;
......@@ -33,7 +39,6 @@ interface iOVM_StateTransitioner {
address _ovmContractAddress,
bytes32 _key,
bytes32 _value,
bytes calldata _stateTrieWitness,
bytes calldata _storageTrieWitness
) external;
......@@ -53,7 +58,7 @@ interface iOVM_StateTransitioner {
function commitContractState(
address _ovmContractAddress,
Lib_OVMCodec.Account calldata _account,
Lib_OVMCodec.EVMAccount calldata _account,
bytes calldata _stateTrieWitness
) external;
......
......@@ -4,15 +4,42 @@ pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol";
import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol";
/**
* @title Lib_OVMCodec
*/
library Lib_OVMCodec {
/*******************
* Data Structures *
*******************/
/*************
* Constants *
*************/
bytes constant internal RLP_NULL_BYTES = hex'80';
bytes constant internal NULL_BYTES = bytes('');
bytes32 constant internal NULL_BYTES32 = bytes32('');
bytes32 constant internal KECCAK256_RLP_NULL_BYTES = keccak256(RLP_NULL_BYTES);
bytes32 constant internal KECCAK256_NULL_BYTES = keccak256(NULL_BYTES);
/*********
* Enums *
*********/
enum EOASignatureType {
ETH_SIGNED_MESSAGE,
NATIVE_TRANSACTON
}
enum QueueOrigin {
SEQUENCER_QUEUE,
L1TOL2_QUEUE
}
/***********
* Structs *
***********/
struct Account {
uint256 nonce;
......@@ -45,25 +72,18 @@ library Lib_OVMCodec {
struct Transaction {
uint256 timestamp;
uint256 queueOrigin;
uint256 number;
QueueOrigin l1QueueOrigin;
address l1Txorigin;
address entrypoint;
address origin;
address msgSender;
uint256 gasLimit;
bytes data;
}
struct ProofMatrix {
bool checkNonce;
bool checkBalance;
bool checkStorageRoot;
bool checkCodeHash;
}
struct QueueElement {
uint256 timestamp;
bytes32 batchRoot;
bool isL1ToL2Batch;
bytes32 queueRoot;
uint40 timestamp;
uint32 blockNumber;
}
struct EOATransaction {
......@@ -73,11 +93,6 @@ library Lib_OVMCodec {
bytes data;
}
enum EOASignatureType {
ETH_SIGNED_MESSAGE,
NATIVE_TRANSACTON
}
/*********************************************
* Internal Functions: Encoding and Decoding *
......@@ -97,13 +112,13 @@ library Lib_OVMCodec {
EOATransaction memory _decoded
)
{
Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.toList(Lib_RLPReader.toRlpItem(_transaction));
Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_transaction);
return EOATransaction({
nonce: Lib_RLPReader.toUint(decoded[0]),
gasLimit: Lib_RLPReader.toUint(decoded[2]),
target: Lib_RLPReader.toAddress(decoded[3]),
data: Lib_RLPReader.toBytes(decoded[5])
nonce: Lib_RLPReader.readUint256(decoded[0]),
gasLimit: Lib_RLPReader.readUint256(decoded[2]),
target: Lib_RLPReader.readAddress(decoded[3]),
data: Lib_RLPReader.readBytes(decoded[5])
});
}
......@@ -123,10 +138,10 @@ library Lib_OVMCodec {
{
return abi.encodePacked(
_transaction.timestamp,
_transaction.queueOrigin,
_transaction.number,
_transaction.l1QueueOrigin,
_transaction.l1Txorigin,
_transaction.entrypoint,
_transaction.origin,
_transaction.msgSender,
_transaction.gasLimit,
_transaction.data
);
......@@ -148,4 +163,77 @@ library Lib_OVMCodec {
{
return keccak256(encodeTransaction(_transaction));
}
/**
* Converts an OVM account to an EVM account.
* @param _in OVM account to convert.
* @return _out Converted EVM account.
*/
function toEVMAccount(
Account memory _in
)
internal
pure
returns (
EVMAccount memory _out
)
{
return EVMAccount({
nonce: _in.nonce,
balance: _in.balance,
storageRoot: _in.storageRoot,
codeHash: _in.codeHash
});
}
/**
* @notice RLP-encodes an account state struct.
* @param _account Account state struct.
* @return _encoded RLP-encoded account state.
*/
function encodeEVMAccount(
EVMAccount memory _account
)
internal
pure
returns (
bytes memory _encoded
)
{
bytes[] memory raw = new bytes[](4);
// Unfortunately we can't create this array outright because
// RLPWriter.encodeList will reject fixed-size arrays. Assigning
// index-by-index circumvents this issue.
raw[0] = Lib_RLPWriter.writeUint(_account.nonce);
raw[1] = Lib_RLPWriter.writeUint(_account.balance);
raw[2] = Lib_RLPWriter.writeBytes(abi.encodePacked(_account.storageRoot));
raw[3] = Lib_RLPWriter.writeBytes(abi.encodePacked(_account.codeHash));
return Lib_RLPWriter.writeList(raw);
}
/**
* @notice Decodes an RLP-encoded account state into a useful struct.
* @param _encoded RLP-encoded account state.
* @return _account Account state struct.
*/
function decodeEVMAccount(
bytes memory _encoded
)
internal
pure
returns (
EVMAccount memory _account
)
{
Lib_RLPReader.RLPItem[] memory accountState = Lib_RLPReader.readList(_encoded);
return EVMAccount({
nonce: Lib_RLPReader.readUint256(accountState[0]),
balance: Lib_RLPReader.readUint256(accountState[1]),
storageRoot: Lib_RLPReader.readBytes32(accountState[2]),
codeHash: Lib_RLPReader.readBytes32(accountState[3])
});
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Contract Imports */
import { Ownable } from "./Lib_Ownable.sol";
/**
* @title Lib_AddressManager
*/
contract Lib_AddressManager is Ownable {
/*******************************************
* Contract Variables: Internal Accounting *
*******************************************/
mapping (bytes32 => address) private addresses;
/********************
* Public Functions *
********************/
function setAddress(
string memory _name,
address _address
)
public
onlyOwner
{
addresses[_getNameHash(_name)] = _address;
}
function getAddress(
string memory _name
)
public
view
returns (address)
{
return addresses[_getNameHash(_name)];
}
/**********************
* Internal Functions *
**********************/
function _getNameHash(
string memory _name
)
internal
pure
returns (
bytes32 _hash
)
{
return keccak256(abi.encodePacked(_name));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_AddressManager } from "./Lib_AddressManager.sol";
/**
* @title Lib_AddressResolver
*/
contract Lib_AddressResolver {
/*******************************************
* Contract Variables: Contract References *
*******************************************/
Lib_AddressManager internal libAddressManager;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address of the Lib_AddressManager.
*/
constructor(
address _libAddressManager
)
public
{
libAddressManager = Lib_AddressManager(_libAddressManager);
}
/********************
* Public Functions *
********************/
function resolve(
string memory _name
)
public
view
returns (
address _contract
)
{
return libAddressManager.getAddress(_name);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
/**
* @title Ownable
* @dev Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
*/
abstract contract Ownable {
/*************
* Variables *
*************/
address public owner;
/**********
* Events *
**********/
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/***************
* Constructor *
***************/
constructor()
internal
{
owner = msg.sender;
emit OwnershipTransferred(address(0), owner);
}
/**********************
* Function Modifiers *
**********************/
modifier onlyOwner() {
require(
owner == msg.sender,
"Ownable: caller is not the owner"
);
_;
}
/********************
* Public Functions *
********************/
function renounceOwnership()
public
virtual
onlyOwner
{
emit OwnershipTransferred(owner, address(0));
owner = address(0);
}
function transferOwnership(address _newOwner)
public
virtual
onlyOwner
{
require(
_newOwner != address(0),
"Ownable: new owner cannot be the zero address"
);
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title RLPReader
* @author Hamdi Allam hamdi.allam97@gmail.com
* @title Lib_RLPReader
* @dev Adapted from "RLPReader" by Hamdi Allam (hamdi.allam97@gmail.com).
*/
library Lib_RLPReader {
/*
* Data Structures
*/
struct RLPItem {
uint len;
uint memPtr;
/*************
* Constants *
*************/
uint256 constant internal MAX_LIST_LENGTH = 32;
/*********
* Enums *
*********/
enum RLPItemType {
DATA_ITEM,
LIST_ITEM
}
/*
* Contract Constants
*/
/***********
* Structs *
***********/
uint8 constant private STRING_SHORT_START = 0x80;
uint8 constant private STRING_LONG_START = 0xb8;
uint8 constant private LIST_SHORT_START = 0xc0;
uint8 constant private LIST_LONG_START = 0xf8;
uint8 constant private WORD_SIZE = 32;
struct RLPItem {
uint256 length;
uint256 ptr;
}
/*
* Public Functions
*/
/**********************
* Internal Functions *
**********************/
/**
* @param item RLP encoded bytes
* Converts bytes to a reference to memory position and length.
* @param _in Input bytes to convert.
* @return Output memory reference.
*/
function toRlpItem(
bytes memory item
function toRLPItem(
bytes memory _in
)
internal
pure
returns (RLPItem memory)
returns (
RLPItem memory
)
{
uint memPtr;
uint256 ptr;
assembly {
memPtr := add(item, 0x20)
ptr := add(_in, 32)
}
return RLPItem(item.length, memPtr);
return RLPItem({
length: _in.length,
ptr: ptr
});
}
/**
* @param item RLP encoded bytes
* Reads an RLP list value into a list of RLP items.
* @param _in RLP list value.
* @return Decoded RLP list items.
*/
function rlpLen(
RLPItem memory item
function readList(
RLPItem memory _in
)
internal
pure
returns (uint)
returns (
RLPItem[] memory
)
{
return item.len;
(
uint256 listOffset,
uint256 listLength,
RLPItemType itemType
) = _decodeLength(_in);
require(
itemType == RLPItemType.LIST_ITEM,
"Invalid RLP list value."
);
// Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by
// writing to the length. Since we can't know the number of RLP items without looping over
// the entire input, we'd have to loop twice to accurately size this array. It's easier to
// simply set a reasonable maximum list length and decrease the size before we finish.
RLPItem[] memory out = new RLPItem[](MAX_LIST_LENGTH);
uint256 itemCount = 0;
uint256 offset = listOffset;
while (offset < _in.length) {
require(
itemCount < MAX_LIST_LENGTH,
"Provided RLP list exceeds max list length."
);
(
uint256 itemOffset,
uint256 itemLength,
) = _decodeLength(RLPItem({
length: _in.length - offset,
ptr: _in.ptr + offset
}));
out[itemCount] = RLPItem({
length: itemLength + itemOffset,
ptr: _in.ptr + offset
});
itemCount += 1;
offset += itemOffset + itemLength;
}
// Decrease the array size to match the actual item count.
assembly {
mstore(out, itemCount)
}
return out;
}
/**
* @param item RLP encoded bytes
* Reads an RLP list value into a list of RLP items.
* @param _in RLP list value.
* @return Decoded RLP list items.
*/
function payloadLen(
RLPItem memory item
function readList(
bytes memory _in
)
internal
pure
returns (uint)
returns (
RLPItem[] memory
)
{
return item.len - _payloadOffset(item.memPtr);
return readList(
toRLPItem(_in)
);
}
/**
* @param item RLP encoded list in bytes
* Reads an RLP bytes value into bytes.
* @param _in RLP bytes value.
* @return Decoded bytes.
*/
function toList(
RLPItem memory item
function readBytes(
RLPItem memory _in
)
internal
pure
returns (RLPItem[] memory result)
returns (
bytes memory
)
{
require(isList(item));
(
uint256 itemOffset,
uint256 itemLength,
RLPItemType itemType
) = _decodeLength(_in);
uint items = numItems(item);
result = new RLPItem[](items);
require(
itemType == RLPItemType.DATA_ITEM,
"Invalid RLP bytes value."
);
uint memPtr = item.memPtr + _payloadOffset(item.memPtr);
uint dataLen;
for (uint i = 0; i < items; i++) {
dataLen = _itemLength(memPtr);
result[i] = RLPItem(dataLen, memPtr);
memPtr = memPtr + dataLen;
}
return _copy(_in.ptr, itemOffset, itemLength);
}
// @return indicator whether encoded payload is a list. negate this function call for isData.
function isList(
RLPItem memory item
/**
* Reads an RLP bytes value into bytes.
* @param _in RLP bytes value.
* @return Decoded bytes.
*/
function readBytes(
bytes memory _in
)
internal
pure
returns (bool)
returns (
bytes memory
)
{
if (item.len == 0) return false;
uint8 byte0;
uint memPtr = item.memPtr;
assembly {
byte0 := byte(0, mload(memPtr))
return readBytes(
toRLPItem(_in)
);
}
if (byte0 < LIST_SHORT_START)
return false;
return true;
/**
* Reads an RLP string value into a string.
* @param _in RLP string value.
* @return Decoded string.
*/
function readString(
RLPItem memory _in
)
internal
pure
returns (
string memory
)
{
return string(readBytes(_in));
}
/** RLPItem conversions into data types **/
// @returns raw rlp encoding in bytes
function toRlpBytes(
RLPItem memory item
/**
* Reads an RLP string value into a string.
* @param _in RLP string value.
* @return Decoded string.
*/
function readString(
bytes memory _in
)
internal
pure
returns (bytes memory)
returns (
string memory
)
{
bytes memory result = new bytes(item.len);
if (result.length == 0) return result;
return readString(
toRLPItem(_in)
);
}
uint ptr;
/**
* Reads an RLP bytes32 value into a bytes32.
* @param _in RLP bytes32 value.
* @return Decoded bytes32.
*/
function readBytes32(
RLPItem memory _in
)
internal
pure
returns (
bytes32
)
{
require(
_in.length <= 33,
"Invalid RLP bytes32 value."
);
(
uint256 itemOffset,
uint256 itemLength,
RLPItemType itemType
) = _decodeLength(_in);
require(
itemType == RLPItemType.DATA_ITEM,
"Invalid RLP bytes32 value."
);
uint256 ptr = _in.ptr + itemOffset;
bytes32 out;
assembly {
ptr := add(0x20, result)
out := mload(ptr)
// Shift the bytes over to match the item size.
if lt(itemLength, 32) {
out := div(out, exp(256, sub(32, itemLength)))
}
}
copy(item.memPtr, ptr, item.len);
return result;
return out;
}
// any non-zero byte is considered true
function toBoolean(
RLPItem memory item
/**
* Reads an RLP bytes32 value into a bytes32.
* @param _in RLP bytes32 value.
* @return Decoded bytes32.
*/
function readBytes32(
bytes memory _in
)
internal
pure
returns (bool)
returns (
bytes32
)
{
require(item.len == 1);
uint result;
uint memPtr = item.memPtr;
assembly {
result := byte(0, mload(memPtr))
return readBytes32(
toRLPItem(_in)
);
}
return result == 0 ? false : true;
/**
* Reads an RLP uint256 value into a uint256.
* @param _in RLP uint256 value.
* @return Decoded uint256.
*/
function readUint256(
RLPItem memory _in
)
internal
pure
returns (
uint256
)
{
return uint256(readBytes32(_in));
}
function toAddress(
RLPItem memory item
/**
* Reads an RLP uint256 value into a uint256.
* @param _in RLP uint256 value.
* @return Decoded uint256.
*/
function readUint256(
bytes memory _in
)
internal
pure
returns (address)
returns (
uint256
)
{
// 1 byte for the length prefix
require(item.len == 21);
return address(toUint(item));
return readUint256(
toRLPItem(_in)
);
}
function toUint(
RLPItem memory item
/**
* Reads an RLP bool value into a bool.
* @param _in RLP bool value.
* @return Decoded bool.
*/
function readBool(
RLPItem memory _in
)
internal
pure
returns (uint)
returns (
bool
)
{
require(item.len > 0 && item.len <= 33);
require(
_in.length == 1,
"Invalid RLP boolean value."
);
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset;
uint result;
uint memPtr = item.memPtr + offset;
uint256 ptr = _in.ptr;
uint256 out;
assembly {
result := mload(memPtr)
// shfit to the correct location if neccesary
if lt(len, 32) {
result := div(result, exp(256, sub(32, len)))
}
out := byte(0, mload(ptr))
}
return result;
return out != 0;
}
// enforces 32 byte length
function toUintStrict(
RLPItem memory item
/**
* Reads an RLP bool value into a bool.
* @param _in RLP bool value.
* @return Decoded bool.
*/
function readBool(
bytes memory _in
)
internal
pure
returns (uint)
returns (
bool
)
{
// one byte prefix
require(item.len == 33);
uint result;
uint memPtr = item.memPtr + 1;
assembly {
result := mload(memPtr)
return readBool(
toRLPItem(_in)
);
}
return result;
}
function toBytes(
RLPItem memory item
/**
* Reads an RLP address value into a address.
* @param _in RLP address value.
* @return Decoded address.
*/
function readAddress(
RLPItem memory _in
)
internal
pure
returns (bytes memory)
returns (
address
)
{
require(item.len > 0);
if (_in.length == 1) {
return address(0);
}
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset; // data length
bytes memory result = new bytes(len);
require(
_in.length == 21,
"Invalid RLP address value."
);
uint destPtr;
assembly {
destPtr := add(0x20, result)
return address(readUint256(_in));
}
copy(item.memPtr + offset, destPtr, len);
return result;
/**
* Reads an RLP address value into a address.
* @param _in RLP address value.
* @return Decoded address.
*/
function readAddress(
bytes memory _in
)
internal
pure
returns (
address
)
{
return readAddress(
toRLPItem(_in)
);
}
/*
* Private Functions
/**
* Reads the raw bytes of an RLP item.
* @param _in RLP item to read.
* @return Raw RLP bytes.
*/
// @return number of payload items inside an encoded list.
function numItems(
RLPItem memory item
function readRawBytes(
RLPItem memory _in
)
private
internal
pure
returns (uint)
returns (
bytes memory
)
{
if (item.len == 0) return 0;
uint count = 0;
uint currPtr = item.memPtr + _payloadOffset(item.memPtr);
uint endPtr = item.memPtr + item.len;
while (currPtr < endPtr) {
currPtr = currPtr + _itemLength(currPtr); // skip over an item
count++;
return _copy(_in);
}
return count;
}
// @return entire rlp item byte length
function _itemLength(
uint memPtr
/*********************
* Private Functions *
*********************/
/**
* Decodes the length of an RLP item.
* @param _in RLP item to decode.
* @return Offset of the encoded data.
* @return Length of the encoded data.
* @return RLP item type (LIST_ITEM or DATA_ITEM).
*/
function _decodeLength(
RLPItem memory _in
)
private
pure
returns (uint len)
returns (
uint256,
uint256,
RLPItemType
)
{
uint byte0;
require(
_in.length > 0,
"RLP item cannot be null."
);
uint256 ptr = _in.ptr;
uint256 prefix;
assembly {
byte0 := byte(0, mload(memPtr))
prefix := byte(0, mload(ptr))
}
if (byte0 < STRING_SHORT_START)
return 1;
if (prefix <= 0x7f) {
// Single byte.
else if (byte0 < STRING_LONG_START)
return byte0 - STRING_SHORT_START + 1;
return (0, 1, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xb7) {
// Short string.
else if (byte0 < LIST_SHORT_START) {
assembly {
let byteLen := sub(byte0, 0xb7) // number of bytes the actual length is
memPtr := add(memPtr, 1) // skip over the first byte
uint256 strLen = prefix - 0x80;
/* 32 byte word size */
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
len := add(dataLen, add(byteLen, 1))
}
}
require(
_in.length > strLen,
"Invalid RLP short string."
);
else if (byte0 < LIST_LONG_START) {
return byte0 - LIST_SHORT_START + 1;
}
return (1, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xbf) {
// Long string.
uint256 lenOfStrLen = prefix - 0xb7;
else {
assembly {
let byteLen := sub(byte0, 0xf7)
memPtr := add(memPtr, 1)
require(
_in.length > lenOfStrLen,
"Invalid RLP long string length."
);
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
len := add(dataLen, add(byteLen, 1))
}
}
uint256 strLen;
assembly {
// Pick out the string length.
strLen := div(
mload(add(ptr, 1)),
exp(256, sub(32, lenOfStrLen))
)
}
// @return number of bytes until the data
function _payloadOffset(
uint memPtr
)
private
pure
returns (uint)
{
uint byte0;
require(
_in.length > lenOfStrLen + strLen,
"Invalid RLP long string."
);
return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xf7) {
// Short list.
uint256 listLen = prefix - 0xc0;
require(
_in.length > listLen,
"Invalid RLP short list."
);
return (1, listLen, RLPItemType.LIST_ITEM);
} else {
// Long list.
uint256 lenOfListLen = prefix - 0xf7;
require(
_in.length > lenOfListLen,
"Invalid RLP long list length."
);
uint256 listLen;
assembly {
byte0 := byte(0, mload(memPtr))
// Pick out the list length.
listLen := div(
mload(add(ptr, 1)),
exp(256, sub(32, lenOfListLen))
)
}
if (byte0 < STRING_SHORT_START)
return 0;
else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START))
return 1;
else if (byte0 < LIST_SHORT_START) // being explicit
return byte0 - (STRING_LONG_START - 1) + 1;
else
return byte0 - (LIST_LONG_START - 1) + 1;
require(
_in.length > lenOfListLen + listLen,
"Invalid RLP long list."
);
return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM);
}
}
/*
* @param src Pointer to source
* @param dest Pointer to destination
* @param len Amount of memory to copy from the source
/**
* Copies the bytes from a memory location.
* @param _src Pointer to the location to read from.
* @param _offset Offset to start reading from.
* @param _length Number of bytes to read.
* @return Copied bytes.
*/
function copy(
uint src,
uint dest,
uint len
function _copy(
uint256 _src,
uint256 _offset,
uint256 _length
)
private
pure
returns (
bytes memory
)
{
if (len == 0) return;
bytes memory out = new bytes(_length);
if (out.length == 0) {
return out;
}
uint256 src = _src + _offset;
uint256 dest;
assembly {
dest := add(out, 32)
}
// copy as many word sizes as possible
for (; len >= WORD_SIZE; len -= WORD_SIZE) {
// Copy over as many complete words as we can.
for (uint256 i = 0; i < _length / 32; i++) {
assembly {
mstore(dest, mload(src))
}
src += WORD_SIZE;
dest += WORD_SIZE;
src += 32;
dest += 32;
}
// left over bytes. Mask is used to remove unwanted bytes from the word
uint mask = 256 ** (WORD_SIZE - len) - 1;
// Pick out the remaining bytes.
uint256 mask = 256 ** (32 - (_length % 32)) - 1;
assembly {
let srcpart := and(mload(src), not(mask)) // zero out src
let destpart := and(mload(dest), mask) // retrieve the bytes
mstore(dest, or(destpart, srcpart))
mstore(
dest,
or(
and(mload(src), not(mask)),
and(mload(dest), mask)
)
)
}
return out;
}
/**
* Copies an RLP item into bytes.
* @param _in RLP item to copy.
* @return Copied bytes.
*/
function _copy(
RLPItem memory _in
)
private
pure
returns (
bytes memory
)
{
return _copy(_in.ptr, 0, _in.length);
}
}
......@@ -2,175 +2,198 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_BytesUtils } from "../utils/Lib_BytesUtils.sol";
/**
* Source: https://github.com/omisego/plasma-mvp/blob/master/plasma/root_chain/contracts/RLPEncode.sol
* @title RLPEncode
* @dev A simple RLP encoding library.
* @author Bakaoh
* @title Lib_RLPWriter
* @author Bakaoh (with modifications)
*/
library Lib_RLPWriter {
/*
* Public Functions
*/
/**********************
* Internal Functions *
**********************/
/**
* RLP encodes a byte string.
* @param self The byte string to encode.
* @return The RLP encoded string in bytes.
* @param _in The byte string to encode.
* @return _out The RLP encoded string in bytes.
*/
function encodeBytes(
bytes memory self
function writeBytes(
bytes memory _in
)
internal
pure
returns (bytes memory)
returns (
bytes memory _out
)
{
bytes memory encoded;
if (self.length == 1 && uint8(self[0]) < 128) {
encoded = self;
if (_in.length == 1 && uint8(_in[0]) < 128) {
encoded = _in;
} else {
encoded = concat(encodeLength(self.length, 128), self);
encoded = Lib_BytesUtils.concat(_writeLength(_in.length, 128), _in);
}
return encoded;
}
/**
* RLP encodes a list of RLP encoded byte byte strings.
* @param self The list of RLP encoded byte strings.
* @return The RLP encoded list of items in bytes.
* @param _in The list of RLP encoded byte strings.
* @return _out The RLP encoded list of items in bytes.
*/
function encodeList(
bytes[] memory self
function writeList(
bytes[] memory _in
)
internal
pure
returns (bytes memory)
returns (
bytes memory _out
)
{
bytes memory list = flatten(self);
return concat(encodeLength(list.length, 192), list);
bytes memory list = _flatten(_in);
return Lib_BytesUtils.concat(_writeLength(list.length, 192), list);
}
/**
* RLP encodes a string.
* @param self The string to encode.
* @return The RLP encoded string in bytes.
* @param _in The string to encode.
* @return _out The RLP encoded string in bytes.
*/
function encodeString(
string memory self
function writeString(
string memory _in
)
internal
pure
returns (bytes memory)
returns (
bytes memory _out
)
{
return encodeBytes(bytes(self));
return writeBytes(bytes(_in));
}
/**
* RLP encodes an address.
* @param self The address to encode.
* @return The RLP encoded address in bytes.
* @param _in The address to encode.
* @return _out The RLP encoded address in bytes.
*/
function encodeAddress(
address self
function writeAddress(
address _in
)
internal
pure
returns (bytes memory)
returns (
bytes memory _out
)
{
bytes memory inputBytes;
assembly {
let m := mload(0x40)
mstore(add(m, 20), xor(0x140000000000000000000000000000000000000000, self))
mstore(add(m, 20), xor(0x140000000000000000000000000000000000000000, _in))
mstore(0x40, add(m, 52))
inputBytes := m
}
return encodeBytes(inputBytes);
return writeBytes(inputBytes);
}
/**
* RLP encodes a uint.
* @param self The uint to encode.
* @return The RLP encoded uint in bytes.
* @param _in The uint to encode.
* @return _out The RLP encoded uint in bytes.
*/
function encodeUint(
uint self
function writeUint(
uint _in
)
internal
pure
returns (bytes memory)
returns (
bytes memory _out
)
{
return encodeBytes(toBinary(self));
return writeBytes(_toBinary(_in));
}
/**
* RLP encodes an int.
* @param self The int to encode.
* @return The RLP encoded int in bytes.
* @param _in The int to encode.
* @return _out The RLP encoded int in bytes.
*/
function encodeInt(
int self
function writeInt(
int _in
)
internal
pure
returns (bytes memory)
returns (
bytes memory _out
)
{
return encodeUint(uint(self));
return writeUint(uint(_in));
}
/**
* RLP encodes a bool.
* @param self The bool to encode.
* @return The RLP encoded bool in bytes.
* @param _in The bool to encode.
* @return _out The RLP encoded bool in bytes.
*/
function encodeBool(
bool self
function writeBool(
bool _in
)
internal
pure
returns (bytes memory)
returns (
bytes memory _out
)
{
bytes memory encoded = new bytes(1);
encoded[0] = (self ? bytes1(0x01) : bytes1(0x80));
encoded[0] = (_in ? bytes1(0x01) : bytes1(0x80));
return encoded;
}
/*
* Private Functions
*/
/*********************
* Private Functions *
*********************/
/**
* Encode the first byte, followed by the `len` in binary form if `length` is more than 55.
* @param len The length of the string or the payload.
* @param offset 128 if item is string, 192 if item is list.
* @return RLP encoded bytes.
* @param _len The length of the string or the payload.
* @param _offset 128 if item is string, 192 if item is list.
* @return _encoded RLP encoded bytes.
*/
function encodeLength(
uint len,
uint offset
function _writeLength(
uint _len,
uint _offset
)
private
pure
returns (bytes memory)
returns (
bytes memory _encoded
)
{
bytes memory encoded;
if (len < 56) {
if (_len < 56) {
encoded = new bytes(1);
encoded[0] = byte(uint8(len) + uint8(offset));
encoded[0] = byte(uint8(_len) + uint8(_offset));
} else {
uint lenLen;
uint i = 1;
while (len / i != 0) {
while (_len / i != 0) {
lenLen++;
i *= 256;
}
encoded = new bytes(lenLen + 1);
encoded[0] = byte(uint8(lenLen) + uint8(offset) + 55);
encoded[0] = byte(uint8(lenLen) + uint8(_offset) + 55);
for(i = 1; i <= lenLen; i++) {
encoded[i] = byte(uint8((len / (256**(lenLen-i))) % 256));
encoded[i] = byte(uint8((_len / (256**(lenLen-i))) % 256));
}
}
return encoded;
}
......@@ -178,29 +201,34 @@ library Lib_RLPWriter {
* Encode integer in big endian binary form with no leading zeroes.
* @notice TODO: This should be optimized with assembly to save gas costs.
* @param _x The integer to encode.
* @return RLP encoded bytes.
* @return _binary RLP encoded bytes.
*/
function toBinary(
function _toBinary(
uint _x
)
private
pure
returns (bytes memory)
returns (
bytes memory _binary
)
{
bytes memory b = new bytes(32);
assembly {
mstore(add(b, 32), _x)
}
uint i = 0;
for (; i < 32; i++) {
if (b[i] != 0) {
break;
}
}
bytes memory res = new bytes(32 - i);
for (uint j = 0; j < res.length; j++) {
res[j] = b[i++];
}
return res;
}
......@@ -211,7 +239,7 @@ library Lib_RLPWriter {
* @param _src Source location.
* @param _len Length of memory to copy.
*/
function memcpy(
function _memcpy(
uint _dest,
uint _src,
uint _len
......@@ -243,14 +271,16 @@ library Lib_RLPWriter {
* Flattens a list of byte strings into one byte string.
* @notice From: https://github.com/sammayo/solidity-rlp-encoder/blob/master/RLPEncode.sol.
* @param _list List of byte strings to flatten.
* @return The flattened byte string.
* @return _flattened The flattened byte string.
*/
function flatten(
function _flatten(
bytes[] memory _list
)
private
pure
returns (bytes memory)
returns (
bytes memory _flattened
)
{
if (_list.length == 0) {
return new bytes(0);
......@@ -272,69 +302,10 @@ library Lib_RLPWriter {
uint listPtr;
assembly { listPtr := add(item, 0x20)}
memcpy(flattenedPtr, listPtr, item.length);
_memcpy(flattenedPtr, listPtr, item.length);
flattenedPtr += _list[i].length;
}
return flattened;
}
/**
* Concatenates two bytes.
* @notice From: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol.
* @param _preBytes First byte string.
* @param _postBytes Second byte string.
* @return Both byte string combined.
*/
function concat(
bytes memory _preBytes,
bytes memory _postBytes
)
private
pure
returns (bytes memory)
{
bytes memory tempBytes;
assembly {
tempBytes := mload(0x40)
let length := mload(_preBytes)
mstore(tempBytes, length)
let mc := add(tempBytes, 0x20)
let end := add(mc, length)
for {
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))
mc := end
end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(0x40, and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31)
))
}
return tempBytes;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_SecureMerkleTrie } from "./Lib_SecureMerkleTrie.sol";
import { Lib_OVMCodec } from "../codec/Lib_OVMCodec.sol";
import { Lib_ByteUtils } from "../utils/Lib_ByteUtils.sol";
import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol";
import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol";
/**
* @title Lib_EthMerkleTrie
*/
library Lib_EthMerkleTrie {
/**********************
* Contract Constants *
**********************/
bytes constant private RLP_NULL_BYTES = hex'80';
bytes32 constant private BYTES32_NULL = bytes32('');
uint256 constant private UINT256_NULL = uint256(0);
/*************************************
* Internal Functions: Storage Slots *
*************************************/
/**
* @notice Verifies a proof for the value of an account storage slot.
* @param _address Address of the contract account.
* @param _key Key for the storage slot.
* @param _value Value for the storage slot.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _storageTrieWitness Inclusion proof for the specific storage
* slot associated with the given key.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the k/v pair is included, `false` otherwise.
*/
function proveAccountStorageSlotValue(
address _address,
bytes32 _key,
bytes32 _value,
bytes memory _stateTrieWitness,
bytes memory _storageTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
// Retrieve the current storage root.
Lib_OVMCodec.EVMAccount memory accountState = getAccountState(
_address,
_stateTrieWitness,
_stateTrieRoot
);
// Verify inclusion of the given k/v pair in the storage trie.
return Lib_SecureMerkleTrie.verifyInclusionProof(
abi.encodePacked(_key),
abi.encodePacked(_value),
_storageTrieWitness,
accountState.storageRoot
);
}
/**
* @notice Updates the value for a given account storage slot.
* @param _address Address of the contract account.
* @param _key Key for the storage slot.
* @param _value New value for the storage slot.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _storageTrieWitness Inclusion proof for the specific storage
* slot associated with the given key.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountStorageSlotValue(
address _address,
bytes32 _key,
bytes32 _value,
bytes memory _stateTrieWitness,
bytes memory _storageTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
// Retreive the old storage root.
Lib_OVMCodec.EVMAccount memory accountState = getAccountState(
_address,
_stateTrieWitness,
_stateTrieRoot
);
// Generate a new storage root.
accountState.storageRoot = Lib_SecureMerkleTrie.update(
abi.encodePacked(_key),
abi.encodePacked(_value),
_storageTrieWitness,
accountState.storageRoot
);
// Update the state trie with the new storage root.
return setAccountState(
accountState,
_address,
_stateTrieWitness,
_stateTrieRoot
);
}
/**************************************
* Internal Functions: Account Proofs *
*************************************/
/**
* @notice Verifies a proof of the current state for a given account.
* @param _address Address of the target account.
* @param _accountState Account state object to verify.
* @param _proofMatrix Matrix of fields to verify or ignore.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the given account state is valid, `false` otherwise.
*/
function proveAccountState(
address _address,
Lib_OVMCodec.EVMAccount memory _accountState,
Lib_OVMCodec.ProofMatrix memory _proofMatrix,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
// Pull the current account state.
Lib_OVMCodec.EVMAccount memory accountState = getAccountState(
_address,
_stateTrieWitness,
_stateTrieRoot
);
// Check each provided component conditionally.
return (
(!_proofMatrix.checkNonce || accountState.nonce == _accountState.nonce) &&
(!_proofMatrix.checkBalance || accountState.balance == _accountState.balance) &&
(!_proofMatrix.checkStorageRoot || accountState.storageRoot == _accountState.storageRoot) &&
(!_proofMatrix.checkCodeHash || accountState.codeHash == _accountState.codeHash)
);
}
/**
* @notice Verifies a proof of the current state for a given account.
* @param _address Address of the target account.
* @param _accountState Account state object to verify.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the given account state is valid, `false` otherwise.
*/
function proveAccountState(
address _address,
Lib_OVMCodec.EVMAccount memory _accountState,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
return proveAccountState(
_address,
_accountState,
Lib_OVMCodec.ProofMatrix({
checkNonce: true,
checkBalance: true,
checkStorageRoot: true,
checkCodeHash: true
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Verifies a proof of the current state for a given account.
* @param _address Address of the target account.
* @param _accountState Account state object to verify.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the given account state is valid, `false` otherwise.
*/
function proveAccountState(
address _address,
Lib_OVMCodec.Account memory _accountState,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
return proveAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: _accountState.nonce,
balance: _accountState.balance,
storageRoot: _accountState.storageRoot,
codeHash: _accountState.codeHash
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Verifies a proof of the account nonce.
* @param _address Address of the target account.
* @param _nonce Account transaction nonce.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the given nonce is valid, `false` otherwise.
*/
function proveAccountNonce(
address _address,
uint256 _nonce,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
return proveAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: _nonce,
balance: UINT256_NULL,
storageRoot: BYTES32_NULL,
codeHash: BYTES32_NULL
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: true,
checkBalance: false,
checkStorageRoot: false,
checkCodeHash: false
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Verifies a proof of the account balance.
* @param _address Address of the target account.
* @param _balance Account balance in wei.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the given balance is valid, `false` otherwise.
*/
function proveAccountBalance(
address _address,
uint256 _balance,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
return proveAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: UINT256_NULL,
balance: _balance,
storageRoot: BYTES32_NULL,
codeHash: BYTES32_NULL
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: false,
checkBalance: true,
checkStorageRoot: false,
checkCodeHash: false
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Verifies a proof of the account storage root.
* @param _address Address of the target account.
* @param _storageRoot Account storage root, empty if EOA.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the given storage root is valid, `false` otherwise.
*/
function proveAccountStorageRoot(
address _address,
bytes32 _storageRoot,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
return proveAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: UINT256_NULL,
balance: UINT256_NULL,
storageRoot: _storageRoot,
codeHash: BYTES32_NULL
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: false,
checkBalance: false,
checkStorageRoot: true,
checkCodeHash: false
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Verifies a proof of the account code hash.
* @param _address Address of the target account.
* @param _codeHash Account code hash, empty if EOA.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return `true` if the given code hash is valid, `false` otherwise.
*/
function proveAccountCodeHash(
address _address,
bytes32 _codeHash,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bool)
{
return proveAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: UINT256_NULL,
balance: UINT256_NULL,
storageRoot: BYTES32_NULL,
codeHash: _codeHash
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: false,
checkBalance: false,
checkStorageRoot: false,
checkCodeHash: true
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/***************************************
* Internal Functions: Account Updates *
***************************************/
/**
* @notice Updates the current state for a given account.
* @param _address Address of the target account.
* @param _accountState Account state to insert.
* @param _proofMatrix Matrix of fields to update or ignore.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountState(
address _address,
Lib_OVMCodec.EVMAccount memory _accountState,
Lib_OVMCodec.ProofMatrix memory _proofMatrix,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
Lib_OVMCodec.EVMAccount memory newAccountState = _accountState;
// If the user has provided everything, don't bother pulling the
// current account state.
if (
!_proofMatrix.checkNonce ||
!_proofMatrix.checkBalance ||
!_proofMatrix.checkStorageRoot ||
!_proofMatrix.checkCodeHash
) {
// Pull the old account state.
Lib_OVMCodec.EVMAccount memory oldAccountState = getAccountState(
_address,
_stateTrieWitness,
_stateTrieRoot
);
// Conditionally update elements that haven't been provided with
// elements from the old account state.
if (!_proofMatrix.checkNonce) {
newAccountState.nonce = oldAccountState.nonce;
}
if (!_proofMatrix.checkBalance) {
newAccountState.balance = oldAccountState.balance;
}
if (!_proofMatrix.checkStorageRoot) {
newAccountState.storageRoot = oldAccountState.storageRoot;
}
if (!_proofMatrix.checkCodeHash) {
newAccountState.codeHash = oldAccountState.codeHash;
}
}
// Update the account state.
return setAccountState(
newAccountState,
_address,
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Updates the current state for a given account.
* @param _address Address of the target account.
* @param _accountState Account state to insert.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountState(
address _address,
Lib_OVMCodec.EVMAccount memory _accountState,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
return updateAccountState(
_address,
_accountState,
Lib_OVMCodec.ProofMatrix({
checkNonce: true,
checkBalance: true,
checkStorageRoot: true,
checkCodeHash: true
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Updates the current state for a given account.
* @param _address Address of the target account.
* @param _accountState Account state to insert.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountState(
address _address,
Lib_OVMCodec.Account memory _accountState,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
return updateAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: _accountState.nonce,
balance: _accountState.balance,
storageRoot: _accountState.storageRoot,
codeHash: _accountState.codeHash
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Updates an account nonce.
* @param _address Address of the target account.
* @param _nonce New account transaction nonce.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountNonce(
address _address,
uint256 _nonce,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
return updateAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: _nonce,
balance: UINT256_NULL,
storageRoot: BYTES32_NULL,
codeHash: BYTES32_NULL
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: true,
checkBalance: false,
checkStorageRoot: false,
checkCodeHash: false
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Updates an account balance.
* @param _address Address of the target account.
* @param _balance New account balance in wei.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountBalance(
address _address,
uint256 _balance,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
return updateAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: UINT256_NULL,
balance: _balance,
storageRoot: BYTES32_NULL,
codeHash: BYTES32_NULL
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: false,
checkBalance: true,
checkStorageRoot: false,
checkCodeHash: false
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Updates an account storage root.
* @param _address Address of the target account.
* @param _storageRoot New account storage root, empty if EOA.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountStorageRoot(
address _address,
bytes32 _storageRoot,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
return updateAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: UINT256_NULL,
balance: UINT256_NULL,
storageRoot: _storageRoot,
codeHash: BYTES32_NULL
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: false,
checkBalance: false,
checkStorageRoot: true,
checkCodeHash: false
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/**
* @notice Updates an account code hash.
* @param _address Address of the target account.
* @param _codeHash New account code hash, empty if EOA.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function updateAccountCodeHash(
address _address,
bytes32 _codeHash,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
internal
view
returns (bytes32)
{
return updateAccountState(
_address,
Lib_OVMCodec.EVMAccount({
nonce: UINT256_NULL,
balance: UINT256_NULL,
storageRoot: BYTES32_NULL,
codeHash: _codeHash
}),
Lib_OVMCodec.ProofMatrix({
checkNonce: false,
checkBalance: false,
checkStorageRoot: false,
checkCodeHash: true
}),
_stateTrieWitness,
_stateTrieRoot
);
}
/*********************
* Private Functions *
*********************/
/**
* @notice Decodes an RLP-encoded account state into a useful struct.
* @param _encodedAccountState RLP-encoded account state.
* @return Account state struct.
*/
function decodeAccountState(
bytes memory _encodedAccountState
)
private
view
returns (Lib_OVMCodec.EVMAccount memory)
{
Lib_RLPReader.RLPItem[] memory accountState = Lib_RLPReader.toList(Lib_RLPReader.toRlpItem(_encodedAccountState));
return Lib_OVMCodec.EVMAccount({
nonce: Lib_RLPReader.toUint(accountState[0]),
balance: Lib_RLPReader.toUint(accountState[1]),
storageRoot: Lib_ByteUtils.toBytes32(Lib_RLPReader.toBytes(accountState[2])),
codeHash: Lib_ByteUtils.toBytes32(Lib_RLPReader.toBytes(accountState[3]))
});
}
/**
* @notice RLP-encodes an account state struct.
* @param _accountState Account state struct.
* @return RLP-encoded account state.
*/
function encodeAccountState(
Lib_OVMCodec.EVMAccount memory _accountState
)
private
view
returns (bytes memory)
{
bytes[] memory raw = new bytes[](4);
// Unfortunately we can't create this array outright because
// RLPWriter.encodeList will reject fixed-size arrays. Assigning
// index-by-index circumvents this issue.
raw[0] = Lib_RLPWriter.encodeUint(_accountState.nonce);
raw[1] = Lib_RLPWriter.encodeUint(_accountState.balance);
raw[2] = _accountState.storageRoot == 0 ? RLP_NULL_BYTES : Lib_RLPWriter.encodeBytes(abi.encodePacked(_accountState.storageRoot));
raw[3] = _accountState.codeHash == 0 ? RLP_NULL_BYTES : Lib_RLPWriter.encodeBytes(abi.encodePacked(_accountState.codeHash));
return Lib_RLPWriter.encodeList(raw);
}
/**
* @notice Retrieves the current account state and converts into a struct.
* @param _address Account address.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
*/
function getAccountState(
address _address,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
private
view
returns (Lib_OVMCodec.EVMAccount memory)
{
Lib_OVMCodec.EVMAccount memory DEFAULT_ACCOUNT_STATE = Lib_OVMCodec.EVMAccount({
nonce: UINT256_NULL,
balance: UINT256_NULL,
storageRoot: keccak256(hex'80'),
codeHash: keccak256(hex'')
});
(
bool exists,
bytes memory encodedAccountState
) = Lib_SecureMerkleTrie.get(
abi.encodePacked(_address),
_stateTrieWitness,
_stateTrieRoot
);
return exists ? decodeAccountState(encodedAccountState) : DEFAULT_ACCOUNT_STATE;
}
/**
* @notice Updates the current account state for a given address.
* @param _accountState New account state, as a struct.
* @param _address Account address.
* @param _stateTrieWitness Inclusion proof for the account state within
* the state trie.
* @param _stateTrieRoot Known root of the state trie.
* @return Root hash of the updated state trie.
*/
function setAccountState(
Lib_OVMCodec.EVMAccount memory _accountState,
address _address,
bytes memory _stateTrieWitness,
bytes32 _stateTrieRoot
)
private
view
returns (bytes32)
{
bytes memory encodedAccountState = encodeAccountState(_accountState);
return Lib_SecureMerkleTrie.update(
abi.encodePacked(_address),
encodedAccountState,
_stateTrieWitness,
_stateTrieRoot
);
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_ByteUtils } from "../utils/Lib_ByteUtils.sol";
import { Lib_BytesUtils } from "../utils/Lib_BytesUtils.sol";
import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol";
import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol";
......@@ -51,6 +51,7 @@ library Lib_MerkleTrie {
// Just a utility constant. RLP represents `NULL` as 0x80.
bytes1 constant RLP_NULL = bytes1(0x80);
bytes constant RLP_NULL_BYTES = hex'80';
bytes32 constant internal KECCAK256_RLP_NULL_BYTES = keccak256(RLP_NULL_BYTES);
/**********************
......@@ -67,7 +68,7 @@ library Lib_MerkleTrie {
* of a list of RLP-encoded nodes that make a path down to the target node.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @return `true` if the k/v pair exists in the trie, `false` otherwise.
* @return _verified `true` if the k/v pair exists in the trie, `false` otherwise.
*/
function verifyInclusionProof(
bytes memory _key,
......@@ -78,37 +79,45 @@ library Lib_MerkleTrie {
internal
view
returns (
bool
bool _verified
)
{
return _verifyProof(_key, _value, _proof, _root, true);
(
bool exists,
bytes memory value
) = get(_key, _proof, _root);
return (
exists && Lib_BytesUtils.equal(_value, value)
);
}
/**
* @notice Verifies a proof that a given key/value pair is *not* present in
* @notice Verifies a proof that a given key is *not* present in
* the Merkle trie.
* @param _key Key of the node to search for, as a hex string.
* @param _value Value of the node to search for, as a hex string.
* @param _proof Merkle trie inclusion proof for the node *nearest* the
* target node. We effectively need to show that either the key exists and
* its value differs, or the key does not exist at all.
* target node.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @return `true` if the k/v pair is absent in the trie, `false` otherwise.
* @return _verified `true` if the key is absent in the trie, `false` otherwise.
*/
function verifyExclusionProof(
bytes memory _key,
bytes memory _value,
bytes memory _proof,
bytes32 _root
)
internal
view
returns (
bool
bool _verified
)
{
return _verifyProof(_key, _value, _proof, _root, false);
(
bool exists,
) = get(_key, _proof, _root);
return exists == false;
}
/**
......@@ -120,7 +129,7 @@ library Lib_MerkleTrie {
* Otherwise, we need to modify the trie to handle the new k/v pair.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @return Root hash of the newly constructed trie.
* @return _updatedRoot Root hash of the newly constructed trie.
*/
function update(
bytes memory _key,
......@@ -131,12 +140,18 @@ library Lib_MerkleTrie {
internal
view
returns (
bytes32
bytes32 _updatedRoot
)
{
// Special case when inserting the very first node.
if (_root == KECCAK256_RLP_NULL_BYTES) {
return keccak256(
_makeLeafNode(_key, _value).encoded
);
}
TrieNode[] memory proof = _parseProof(_proof);
(uint256 pathLength, bytes memory keyRemainder, ) = _walkNodePath(proof, _key, _root);
TrieNode[] memory newPath = _getNewPath(proof, pathLength, keyRemainder, _value);
return _getUpdatedTrieRoot(newPath, _key);
......@@ -147,7 +162,8 @@ library Lib_MerkleTrie {
* @param _key Key to search for, as hex bytes.
* @param _proof Merkle trie inclusion proof for the key.
* @param _root Known root of the Merkle trie.
* @return Whether the node exists, value associated with the key if so.
* @return _exists Whether or not the key exists.
* @return _value Value of the key if it exists.
*/
function get(
bytes memory _key,
......@@ -157,14 +173,20 @@ library Lib_MerkleTrie {
internal
view
returns (
bool,
bytes memory
bool _exists,
bytes memory _value
)
{
TrieNode[] memory proof = _parseProof(_proof);
(uint256 pathLength, bytes memory keyRemainder, ) = _walkNodePath(proof, _key, _root);
(uint256 pathLength, bytes memory keyRemainder, bool isFinalNode) = _walkNodePath(proof, _key, _root);
bool exists = keyRemainder.length == 0;
require(
exists || isFinalNode,
"Provided proof is invalid."
);
bytes memory value = exists ? _getNodeValue(proof[pathLength - 1]) : bytes('');
return (
......@@ -177,7 +199,7 @@ library Lib_MerkleTrie {
* Computes the root hash for a trie with a single node.
* @param _key Key for the single node.
* @param _value Value for the single node.
* @return Hash of the trie.
* @return _updatedRoot Hash of the trie.
*/
function getSingleNodeRootHash(
bytes memory _key,
......@@ -186,7 +208,7 @@ library Lib_MerkleTrie {
internal
view
returns (
bytes32
bytes32 _updatedRoot
)
{
return keccak256(_makeLeafNode(
......@@ -200,62 +222,14 @@ library Lib_MerkleTrie {
* Private Functions *
*********************/
/**
* @notice Utility function that handles verification of inclusion or
* exclusion proofs. Since the verification methods are almost identical,
* it's easier to shove this into a single function.
* @param _key Key of the node to search for, as a hex string.
* @param _value Value of the node to search for, as a hex string.
* @param _proof Merkle trie inclusion proof for the node *nearest* the
* target node. If we're proving explicit inclusion, the nearest node
* should be the target node.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @param _inclusion Whether to check for inclusion or exclusion.
* @return `true` if the k/v pair is (in/not in) the trie, `false` otherwise.
*/
function _verifyProof(
bytes memory _key,
bytes memory _value,
bytes memory _proof,
bytes32 _root,
bool _inclusion
)
private
view
returns (
bool
)
{
TrieNode[] memory proof = _parseProof(_proof);
(uint256 pathLength, bytes memory keyRemainder, bool isFinalNode) = _walkNodePath(proof, _key, _root);
if (_inclusion) {
// Included leaf nodes should have no key remainder, values should match.
return (
keyRemainder.length == 0 &&
Lib_ByteUtils.equal(_getNodeValue(proof[pathLength - 1]), _value)
);
} else {
// If there's no key remainder then a leaf with the given key exists and the value should differ.
// Otherwise, we need to make sure that we've hit a dead end.
return (
(keyRemainder.length == 0 && !Lib_ByteUtils.equal(_getNodeValue(proof[pathLength - 1]), _value)) ||
(keyRemainder.length != 0 && isFinalNode)
);
}
}
/**
* @notice Walks through a proof using a provided key.
* @param _proof Inclusion proof to walk through.
* @param _key Key to use for the walk.
* @param _root Known root of the trie.
* @return (
* Length of the final path;
* Portion of the key remaining after the walk;
* Whether or not we've hit a dead end;
* )
* @return _pathLength Length of the final path
* @return _keyRemainder Portion of the key remaining after the walk.
* @return _isFinalNode Whether or not we've hit a dead end.
*/
function _walkNodePath(
TrieNode[] memory _proof,
......@@ -265,13 +239,13 @@ library Lib_MerkleTrie {
private
view
returns (
uint256,
bytes memory,
bool
uint256 _pathLength,
bytes memory _keyRemainder,
bool _isFinalNode
)
{
uint256 pathLength = 0;
bytes memory key = Lib_ByteUtils.toNibbles(_key);
bytes memory key = Lib_BytesUtils.toNibbles(_key);
bytes32 currentNodeID = _root;
uint256 currentKeyIndex = 0;
......@@ -302,7 +276,7 @@ library Lib_MerkleTrie {
} else {
// Nodes smaller than 31 bytes aren't hashed.
require(
Lib_ByteUtils.toBytes32(currentNode.encoded) == currentNodeID,
Lib_BytesUtils.toBytes32(currentNode.encoded) == currentNodeID,
"Invalid internal node hash"
);
}
......@@ -324,8 +298,8 @@ library Lib_MerkleTrie {
bytes memory path = _getNodePath(currentNode);
uint8 prefix = uint8(path[0]);
uint8 offset = 2 - prefix % 2;
bytes memory pathRemainder = Lib_ByteUtils.slice(path, offset);
bytes memory keyRemainder = Lib_ByteUtils.slice(key, currentKeyIndex);
bytes memory pathRemainder = Lib_BytesUtils.slice(path, offset);
bytes memory keyRemainder = Lib_BytesUtils.slice(key, currentKeyIndex);
uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder);
if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) {
......@@ -355,12 +329,14 @@ library Lib_MerkleTrie {
continue;
}
}
} else {
revert("Received an unparseable node.");
}
}
// If our node ID is NULL, then we're at a dead end.
bool isFinalNode = currentNodeID == bytes32(RLP_NULL);
return (pathLength, Lib_ByteUtils.slice(key, currentKeyIndex), isFinalNode);
return (pathLength, Lib_BytesUtils.slice(key, currentKeyIndex), isFinalNode);
}
/**
......@@ -373,7 +349,7 @@ library Lib_MerkleTrie {
* @param _keyRemainder Portion of the initial key that must be inserted
* into the trie.
* @param _value Value to insert at the given key.
* @return A new path with the inserted k/v pair and extra supporting nodes.
* @return _newPath A new path with the inserted k/v pair and extra supporting nodes.
*/
function _getNewPath(
TrieNode[] memory _path,
......@@ -384,7 +360,7 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode[] memory
TrieNode[] memory _newPath
)
{
bytes memory keyRemainder = _keyRemainder;
......@@ -418,7 +394,7 @@ library Lib_MerkleTrie {
totalNewNodes += 1;
// Create a new leaf node, slicing our remainder since the first byte points
// to our branch node.
newNodes[totalNewNodes] = _makeLeafNode(Lib_ByteUtils.slice(keyRemainder, 1), _value);
newNodes[totalNewNodes] = _makeLeafNode(Lib_BytesUtils.slice(keyRemainder, 1), _value);
totalNewNodes += 1;
}
} else {
......@@ -429,13 +405,13 @@ library Lib_MerkleTrie {
if (sharedNibbleLength != 0) {
// We've got some shared nibbles between the last node and our key remainder.
// We'll need to insert an extension node that covers these shared nibbles.
bytes memory nextNodeKey = Lib_ByteUtils.slice(lastNodeKey, 0, sharedNibbleLength);
bytes memory nextNodeKey = Lib_BytesUtils.slice(lastNodeKey, 0, sharedNibbleLength);
newNodes[totalNewNodes] = _makeExtensionNode(nextNodeKey, _getNodeHash(_value));
totalNewNodes += 1;
// Cut down the keys since we've just covered these shared nibbles.
lastNodeKey = Lib_ByteUtils.slice(lastNodeKey, sharedNibbleLength);
keyRemainder = Lib_ByteUtils.slice(keyRemainder, sharedNibbleLength);
lastNodeKey = Lib_BytesUtils.slice(lastNodeKey, sharedNibbleLength);
keyRemainder = Lib_BytesUtils.slice(keyRemainder, sharedNibbleLength);
}
// Create an empty branch to fill in.
......@@ -451,7 +427,7 @@ library Lib_MerkleTrie {
// We're going to modify some index of our branch.
uint8 branchKey = uint8(lastNodeKey[0]);
// Move on to the next nibble.
lastNodeKey = Lib_ByteUtils.slice(lastNodeKey, 1);
lastNodeKey = Lib_BytesUtils.slice(lastNodeKey, 1);
if (lastNodeType == NodeType.LeafNode) {
// We're dealing with a leaf node.
......@@ -482,7 +458,7 @@ library Lib_MerkleTrie {
// We've got some key remainder to work with.
// We'll be inserting a leaf node into the trie.
// First, move on to the next nibble.
keyRemainder = Lib_ByteUtils.slice(keyRemainder, 1);
keyRemainder = Lib_BytesUtils.slice(keyRemainder, 1);
// Push the branch into the list of new nodes.
newNodes[totalNewNodes] = newBranch;
totalNewNodes += 1;
......@@ -501,7 +477,7 @@ library Lib_MerkleTrie {
* @notice Computes the trie root from a given path.
* @param _nodes Path to some k/v pair.
* @param _key Key for the k/v pair.
* @return Root hash for the updated trie.
* @return _updatedRoot Root hash for the updated trie.
*/
function _getUpdatedTrieRoot(
TrieNode[] memory _nodes,
......@@ -510,10 +486,10 @@ library Lib_MerkleTrie {
private
view
returns (
bytes32
bytes32 _updatedRoot
)
{
bytes memory key = Lib_ByteUtils.toNibbles(_key);
bytes memory key = Lib_BytesUtils.toNibbles(_key);
// Some variables to keep track of during iteration.
TrieNode memory currentNode;
......@@ -530,11 +506,11 @@ library Lib_MerkleTrie {
// Leaf nodes are already correctly encoded.
// Shift the key over to account for the nodes key.
bytes memory nodeKey = _getNodeKey(currentNode);
key = Lib_ByteUtils.slice(key, 0, key.length - nodeKey.length);
key = Lib_BytesUtils.slice(key, 0, key.length - nodeKey.length);
} else if (currentNodeType == NodeType.ExtensionNode) {
// Shift the key over to account for the nodes key.
bytes memory nodeKey = _getNodeKey(currentNode);
key = Lib_ByteUtils.slice(key, 0, key.length - nodeKey.length);
key = Lib_BytesUtils.slice(key, 0, key.length - nodeKey.length);
// If this node is the last element in the path, it'll be correctly encoded
// and we can skip this part.
......@@ -548,7 +524,7 @@ library Lib_MerkleTrie {
if (previousNodeHash.length > 0) {
// Re-encode the node based on the previous node.
uint8 branchKey = uint8(key[key.length - 1]);
key = Lib_ByteUtils.slice(key, 0, key.length - 1);
key = Lib_BytesUtils.slice(key, 0, key.length - 1);
currentNode = _editBranchIndex(currentNode, branchKey, previousNodeHash);
}
}
......@@ -565,7 +541,7 @@ library Lib_MerkleTrie {
/**
* @notice Parses an RLP-encoded proof into something more useful.
* @param _proof RLP-encoded proof to parse.
* @return Proof parsed into easily accessible structs.
* @return _parsed Proof parsed into easily accessible structs.
*/
function _parseProof(
bytes memory _proof
......@@ -573,17 +549,17 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode[] memory
TrieNode[] memory _parsed
)
{
Lib_RLPReader.RLPItem[] memory nodes = Lib_RLPReader.toList(Lib_RLPReader.toRlpItem(_proof));
Lib_RLPReader.RLPItem[] memory nodes = Lib_RLPReader.readList(_proof);
TrieNode[] memory proof = new TrieNode[](nodes.length);
for (uint256 i = 0; i < nodes.length; i++) {
bytes memory encoded = Lib_RLPReader.toBytes(nodes[i]);
bytes memory encoded = Lib_RLPReader.readBytes(nodes[i]);
proof[i] = TrieNode({
encoded: encoded,
decoded: Lib_RLPReader.toList(Lib_RLPReader.toRlpItem(encoded))
decoded: Lib_RLPReader.readList(encoded)
});
}
......@@ -595,7 +571,7 @@ library Lib_MerkleTrie {
* "hash" within the specification, but nodes < 32 bytes are not actually
* hashed.
* @param _node Node to pull an ID for.
* @return ID for the node, depending on the size of its contents.
* @return _nodeID ID for the node, depending on the size of its contents.
*/
function _getNodeID(
Lib_RLPReader.RLPItem memory _node
......@@ -603,26 +579,26 @@ library Lib_MerkleTrie {
private
view
returns (
bytes32
bytes32 _nodeID
)
{
bytes memory nodeID;
if (_node.len < 32) {
if (_node.length < 32) {
// Nodes smaller than 32 bytes are RLP encoded.
nodeID = Lib_RLPReader.toRlpBytes(_node);
nodeID = Lib_RLPReader.readRawBytes(_node);
} else {
// Nodes 32 bytes or larger are hashed.
nodeID = Lib_RLPReader.toBytes(_node);
nodeID = Lib_RLPReader.readBytes(_node);
}
return Lib_ByteUtils.toBytes32(nodeID);
return Lib_BytesUtils.toBytes32(nodeID);
}
/**
* @notice Gets the path for a leaf or extension node.
* @param _node Node to get a path for.
* @return Node path, converted to an array of nibbles.
* @return _path Node path, converted to an array of nibbles.
*/
function _getNodePath(
TrieNode memory _node
......@@ -630,17 +606,17 @@ library Lib_MerkleTrie {
private
view
returns (
bytes memory
bytes memory _path
)
{
return Lib_ByteUtils.toNibbles(Lib_RLPReader.toBytes(_node.decoded[0]));
return Lib_BytesUtils.toNibbles(Lib_RLPReader.readBytes(_node.decoded[0]));
}
/**
* @notice Gets the key for a leaf or extension node. Keys are essentially
* just paths without any prefix.
* @param _node Node to get a key for.
* @return Node key, converted to an array of nibbles.
* @return _key Node key, converted to an array of nibbles.
*/
function _getNodeKey(
TrieNode memory _node
......@@ -648,7 +624,7 @@ library Lib_MerkleTrie {
private
view
returns (
bytes memory
bytes memory _key
)
{
return _removeHexPrefix(_getNodePath(_node));
......@@ -657,7 +633,7 @@ library Lib_MerkleTrie {
/**
* @notice Gets the path for a node.
* @param _node Node to get a value for.
* @return Node value, as hex bytes.
* @return _value Node value, as hex bytes.
*/
function _getNodeValue(
TrieNode memory _node
......@@ -665,17 +641,17 @@ library Lib_MerkleTrie {
private
view
returns (
bytes memory
bytes memory _value
)
{
return Lib_RLPReader.toBytes(_node.decoded[_node.decoded.length - 1]);
return Lib_RLPReader.readBytes(_node.decoded[_node.decoded.length - 1]);
}
/**
* @notice Computes the node hash for an encoded node. Nodes < 32 bytes
* are not hashed, all others are keccak256 hashed.
* @param _encoded Encoded node to hash.
* @return Hash of the encoded node. Simply the input if < 32 bytes.
* @return _hash Hash of the encoded node. Simply the input if < 32 bytes.
*/
function _getNodeHash(
bytes memory _encoded
......@@ -683,7 +659,7 @@ library Lib_MerkleTrie {
private
pure
returns (
bytes memory
bytes memory _hash
)
{
if (_encoded.length < 32) {
......@@ -696,7 +672,7 @@ library Lib_MerkleTrie {
/**
* @notice Determines the type for a given node.
* @param _node Node to determine a type for.
* @return Type of the node; BranchNode/ExtensionNode/LeafNode.
* @return _type Type of the node; BranchNode/ExtensionNode/LeafNode.
*/
function _getNodeType(
TrieNode memory _node
......@@ -704,7 +680,7 @@ library Lib_MerkleTrie {
private
view
returns (
NodeType
NodeType _type
)
{
if (_node.decoded.length == BRANCH_NODE_LENGTH) {
......@@ -728,7 +704,7 @@ library Lib_MerkleTrie {
* nibble arrays.
* @param _a First nibble array.
* @param _b Second nibble array.
* @return Number of shared nibbles.
* @return _shared Number of shared nibbles.
*/
function _getSharedNibbleLength(
bytes memory _a,
......@@ -737,7 +713,7 @@ library Lib_MerkleTrie {
private
view
returns (
uint256
uint256 _shared
)
{
uint256 i = 0;
......@@ -750,7 +726,7 @@ library Lib_MerkleTrie {
/**
* @notice Utility; converts an RLP-encoded node into our nice struct.
* @param _raw RLP-encoded node to convert.
* @return Node as a TrieNode struct.
* @return _node Node as a TrieNode struct.
*/
function _makeNode(
bytes[] memory _raw
......@@ -758,21 +734,21 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode memory
TrieNode memory _node
)
{
bytes memory encoded = Lib_RLPWriter.encodeList(_raw);
bytes memory encoded = Lib_RLPWriter.writeList(_raw);
return TrieNode({
encoded: encoded,
decoded: Lib_RLPReader.toList(Lib_RLPReader.toRlpItem(encoded))
decoded: Lib_RLPReader.readList(encoded)
});
}
/**
* @notice Utility; converts an RLP-decoded node into our nice struct.
* @param _items RLP-decoded node to convert.
* @return Node as a TrieNode struct.
* @return _node Node as a TrieNode struct.
*/
function _makeNode(
Lib_RLPReader.RLPItem[] memory _items
......@@ -780,12 +756,12 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode memory
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](_items.length);
for (uint256 i = 0; i < _items.length; i++) {
raw[i] = Lib_RLPReader.toRlpBytes(_items[i]);
raw[i] = Lib_RLPReader.readRawBytes(_items[i]);
}
return _makeNode(raw);
}
......@@ -794,7 +770,7 @@ library Lib_MerkleTrie {
* @notice Creates a new extension node.
* @param _key Key for the extension node, unprefixed.
* @param _value Value for the extension node.
* @return New extension node with the given k/v pair.
* @return _node New extension node with the given k/v pair.
*/
function _makeExtensionNode(
bytes memory _key,
......@@ -803,13 +779,13 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode memory
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](2);
bytes memory key = _addHexPrefix(_key, false);
raw[0] = Lib_RLPWriter.encodeBytes(Lib_ByteUtils.fromNibbles(key));
raw[1] = Lib_RLPWriter.encodeBytes(_value);
raw[0] = Lib_RLPWriter.writeBytes(Lib_BytesUtils.fromNibbles(key));
raw[1] = Lib_RLPWriter.writeBytes(_value);
return _makeNode(raw);
}
......@@ -820,7 +796,7 @@ library Lib_MerkleTrie {
* more gas efficient to keep them separate and duplicate the logic.
* @param _key Key for the leaf node, unprefixed.
* @param _value Value for the leaf node.
* @return New leaf node with the given k/v pair.
* @return _node New leaf node with the given k/v pair.
*/
function _makeLeafNode(
bytes memory _key,
......@@ -829,25 +805,25 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode memory
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](2);
bytes memory key = _addHexPrefix(_key, true);
raw[0] = Lib_RLPWriter.encodeBytes(Lib_ByteUtils.fromNibbles(key));
raw[1] = Lib_RLPWriter.encodeBytes(_value);
raw[0] = Lib_RLPWriter.writeBytes(Lib_BytesUtils.fromNibbles(key));
raw[1] = Lib_RLPWriter.writeBytes(_value);
return _makeNode(raw);
}
/**
* @notice Creates an empty branch node.
* @return Empty branch node as a TrieNode stuct.
* @return _node Empty branch node as a TrieNode stuct.
*/
function _makeEmptyBranchNode()
private
view
returns (
TrieNode memory
TrieNode memory _node
)
{
bytes[] memory raw = new bytes[](BRANCH_NODE_LENGTH);
......@@ -861,7 +837,7 @@ library Lib_MerkleTrie {
* @notice Modifies the value slot for a given branch.
* @param _branch Branch node to modify.
* @param _value Value to insert into the branch.
* @return Modified branch node.
* @return _updatedNode Modified branch node.
*/
function _editBranchValue(
TrieNode memory _branch,
......@@ -870,11 +846,11 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode memory
TrieNode memory _updatedNode
)
{
bytes memory encoded = Lib_RLPWriter.encodeBytes(_value);
_branch.decoded[_branch.decoded.length - 1] = Lib_RLPReader.toRlpItem(encoded);
bytes memory encoded = Lib_RLPWriter.writeBytes(_value);
_branch.decoded[_branch.decoded.length - 1] = Lib_RLPReader.toRLPItem(encoded);
return _makeNode(_branch.decoded);
}
......@@ -883,7 +859,7 @@ library Lib_MerkleTrie {
* @param _branch Branch node to modify.
* @param _index Slot index to modify.
* @param _value Value to insert into the slot.
* @return Modified branch node.
* @return _updatedNode Modified branch node.
*/
function _editBranchIndex(
TrieNode memory _branch,
......@@ -893,11 +869,11 @@ library Lib_MerkleTrie {
private
view
returns (
TrieNode memory
TrieNode memory _updatedNode
)
{
bytes memory encoded = _value.length < 32 ? _value : Lib_RLPWriter.encodeBytes(_value);
_branch.decoded[_index] = Lib_RLPReader.toRlpItem(encoded);
bytes memory encoded = _value.length < 32 ? _value : Lib_RLPWriter.writeBytes(_value);
_branch.decoded[_index] = Lib_RLPReader.toRLPItem(encoded);
return _makeNode(_branch.decoded);
}
......@@ -905,7 +881,7 @@ library Lib_MerkleTrie {
* @notice Utility; adds a prefix to a key.
* @param _key Key to prefix.
* @param _isLeaf Whether or not the key belongs to a leaf.
* @return Prefixed key.
* @return _prefixedKey Prefixed key.
*/
function _addHexPrefix(
bytes memory _key,
......@@ -914,20 +890,20 @@ library Lib_MerkleTrie {
private
view
returns (
bytes memory
bytes memory _prefixedKey
)
{
uint8 prefix = _isLeaf ? uint8(0x02) : uint8(0x00);
uint8 offset = uint8(_key.length % 2);
bytes memory prefixed = new bytes(2 - offset);
prefixed[0] = bytes1(prefix + offset);
return Lib_ByteUtils.concat(prefixed, _key);
return Lib_BytesUtils.concat(prefixed, _key);
}
/**
* @notice Utility; removes a prefix from a path.
* @param _path Path to remove the prefix from.
* @return Unprefixed key.
* @return _unprefixedKey Unprefixed key.
*/
function _removeHexPrefix(
bytes memory _path
......@@ -935,13 +911,13 @@ library Lib_MerkleTrie {
private
view
returns (
bytes memory
bytes memory _unprefixedKey
)
{
if (uint8(_path[0]) % 2 == 0) {
return Lib_ByteUtils.slice(_path, 2);
return Lib_BytesUtils.slice(_path, 2);
} else {
return Lib_ByteUtils.slice(_path, 1);
return Lib_BytesUtils.slice(_path, 1);
}
}
......@@ -953,7 +929,7 @@ library Lib_MerkleTrie {
* @param _aLength Length of the first array.
* @param _b Second array to join.
* @param _bLength Length of the second array.
* @return Combined node array.
* @return _joined Combined node array.
*/
function _joinNodeArrays(
TrieNode[] memory _a,
......@@ -964,7 +940,7 @@ library Lib_MerkleTrie {
private
pure
returns (
TrieNode[] memory
TrieNode[] memory _joined
)
{
TrieNode[] memory ret = new TrieNode[](_aLength + _bLength);
......
......@@ -24,7 +24,7 @@ library Lib_SecureMerkleTrie {
* of a list of RLP-encoded nodes that make a path down to the target node.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @return `true` if the k/v pair exists in the trie, `false` otherwise.
* @return _verified `true` if the k/v pair exists in the trie, `false` otherwise.
*/
function verifyInclusionProof(
bytes memory _key,
......@@ -35,7 +35,7 @@ library Lib_SecureMerkleTrie {
internal
view
returns (
bool
bool _verified
)
{
bytes memory key = _getSecureKey(_key);
......@@ -43,31 +43,28 @@ library Lib_SecureMerkleTrie {
}
/**
* @notice Verifies a proof that a given key/value pair is *not* present in
* @notice Verifies a proof that a given key is *not* present in
* the Merkle trie.
* @param _key Key of the node to search for, as a hex string.
* @param _value Value of the node to search for, as a hex string.
* @param _proof Merkle trie inclusion proof for the node *nearest* the
* target node. We effectively need to show that either the key exists and
* its value differs, or the key does not exist at all.
* target node.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @return `true` if the k/v pair is absent in the trie, `false` otherwise.
* @return _verified `true` if the key is not present in the trie, `false` otherwise.
*/
function verifyExclusionProof(
bytes memory _key,
bytes memory _value,
bytes memory _proof,
bytes32 _root
)
internal
view
returns (
bool
bool _verified
)
{
bytes memory key = _getSecureKey(_key);
return Lib_MerkleTrie.verifyExclusionProof(key, _value, _proof, _root);
return Lib_MerkleTrie.verifyExclusionProof(key, _proof, _root);
}
/**
......@@ -79,7 +76,7 @@ library Lib_SecureMerkleTrie {
* Otherwise, we need to modify the trie to handle the new k/v pair.
* @param _root Known root of the Merkle trie. Used to verify that the
* included proof is correctly constructed.
* @return Root hash of the newly constructed trie.
* @return _updatedRoot Root hash of the newly constructed trie.
*/
function update(
bytes memory _key,
......@@ -90,7 +87,7 @@ library Lib_SecureMerkleTrie {
internal
view
returns (
bytes32
bytes32 _updatedRoot
)
{
bytes memory key = _getSecureKey(_key);
......@@ -102,7 +99,8 @@ library Lib_SecureMerkleTrie {
* @param _key Key to search for, as hex bytes.
* @param _proof Merkle trie inclusion proof for the key.
* @param _root Known root of the Merkle trie.
* @return Whether the node exists, value associated with the key if so.
* @return _exists Whether or not the key exists.
* @return _value Value of the key if it exists.
*/
function get(
bytes memory _key,
......@@ -112,8 +110,8 @@ library Lib_SecureMerkleTrie {
internal
view
returns (
bool,
bytes memory
bool _exists,
bytes memory _value
)
{
bytes memory key = _getSecureKey(_key);
......@@ -124,7 +122,7 @@ library Lib_SecureMerkleTrie {
* Computes the root hash for a trie with a single node.
* @param _key Key for the single node.
* @param _value Value for the single node.
* @return Hash of the trie.
* @return _updatedRoot Hash of the trie.
*/
function getSingleNodeRootHash(
bytes memory _key,
......@@ -133,7 +131,7 @@ library Lib_SecureMerkleTrie {
internal
view
returns (
bytes32
bytes32 _updatedRoot
)
{
bytes memory key = _getSecureKey(_key);
......@@ -145,13 +143,18 @@ library Lib_SecureMerkleTrie {
* Private Functions *
*********************/
/**
* Computes the secure counterpart to a key.
* @param _key Key to get a secure key from.
* @return _secureKey Secure version of the key.
*/
function _getSecureKey(
bytes memory _key
)
private
pure
returns (
bytes memory
bytes memory _secureKey
)
{
return abi.encodePacked(keccak256(_key));
......
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/**
* @title Lib_Byte32Utils
*/
library Lib_Bytes32Utils {
/**********************
* Internal Functions *
**********************/
function toBool(
bytes32 _in
)
internal
pure
returns (
bool _out
)
{
return _in != 0;
}
function fromBool(
bool _in
)
internal
pure
returns (
bytes32 _out
)
{
return bytes32(uint256(_in ? 1 : 0));
}
function toAddress(
bytes32 _in
)
internal
pure
returns (
address _out
)
{
return address(uint160(uint256(_in)));
}
function fromAddress(
address _in
)
internal
pure
returns (
bytes32 _out
)
{
return bytes32(bytes20(_in));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/*
* @title Lib_ByteUtils
/**
* @title Lib_BytesUtils
*/
library Lib_ByteUtils {
library Lib_BytesUtils {
/**********************
* Internal Functions *
......
......@@ -35,15 +35,18 @@ library Lib_ECDSAUtils {
)
{
bytes32 messageHash;
uint8 v;
if (_isEthSignedMessage) {
messageHash = getEthSignedMessageHash(_message);
v = _v;
} else {
messageHash = getNativeMessageHash(_message);
v = (_v - uint8(_chainId) * 2) - 8;
}
return ecrecover(
messageHash,
(_v - uint8(_chainId) * 2) - 8,
v,
_r,
_s
);
......
......@@ -151,10 +151,10 @@ library Lib_EthUtils {
)
{
bytes[] memory encoded = new bytes[](2);
encoded[0] = Lib_RLPWriter.encodeAddress(_creator);
encoded[1] = Lib_RLPWriter.encodeUint(_nonce);
encoded[0] = Lib_RLPWriter.writeAddress(_creator);
encoded[1] = Lib_RLPWriter.writeUint(_nonce);
bytes memory encodedList = Lib_RLPWriter.encodeList(encoded);
bytes memory encodedList = Lib_RLPWriter.writeList(encoded);
return getAddressFromHash(keccak256(encodedList));
}
......
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
......@@ -15,6 +15,11 @@ library Lib_MerkleUtils {
bytes32 _root
)
{
require(
_hashes.length > 0,
"Must provide at least one leaf hash."
);
if (_hashes.length == 1) {
return _hashes[0];
}
......
// 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;
/**
* @title Lib_SafeExecutionManagerWrapper
*/
library Lib_SafeExecutionManagerWrapper {
/**********************
* Internal Functions *
**********************/
/**
* Makes an ovmCALL and performs all the necessary safety checks.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _gasLimit Gas limit for the call.
* @param _target Address to call.
* @param _calldata Data to send to the call.
* @return _success Whether or not the call reverted.
* @return _returndata Data returned by the call.
*/
function safeCALL(
address _ovmExecutionManager,
uint256 _gasLimit,
address _target,
bytes memory _calldata
)
internal
returns (
bool _success,
bytes memory _returndata
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmCALL(uint256,address,bytes)",
_gasLimit,
_target,
_calldata
)
);
return abi.decode(returndata, (bool, bytes));
}
/**
* Performs an ovmCREATE and the necessary safety checks.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _gasLimit Gas limit for the creation.
* @param _bytecode Code for the new contract.
* @return _contract Address of the created contract.
*/
function safeCREATE(
address _ovmExecutionManager,
uint256 _gasLimit,
bytes memory _bytecode
)
internal
returns (
address _contract
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
_gasLimit,
abi.encodeWithSignature(
"ovmCREATE(bytes)",
_bytecode
)
);
return abi.decode(returndata, (address));
}
/**
* Performs a safe ovmCHAINID call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @return _CHAINID Result of calling ovmCHAINID.
*/
function safeCHAINID(
address _ovmExecutionManager
)
internal
returns (
uint256 _CHAINID
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmCHAINID()"
)
);
return abi.decode(returndata, (uint256));
}
/**
* Performs a safe ovmADDRESS call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @return _ADDRESS Result of calling ovmADDRESS.
*/
function safeADDRESS(
address _ovmExecutionManager
)
internal
returns (
address _ADDRESS
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmADDRESS()"
)
);
return abi.decode(returndata, (address));
}
/**
* Performs a safe ovmGETNONCE call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @return _nonce Result of calling ovmGETNONCE.
*/
function safeGETNONCE(
address _ovmExecutionManager
)
internal
returns (
uint256 _nonce
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmGETNONCE()"
)
);
return abi.decode(returndata, (uint256));
}
/**
* Performs a safe ovmSETNONCE call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _nonce New account nonce.
*/
function safeSETNONCE(
address _ovmExecutionManager,
uint256 _nonce
)
internal
{
_safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmSETNONCE(uint256)",
_nonce
)
);
}
/*********************
* Private Functions *
*********************/
/**
* Performs an ovm interaction and the necessary safety checks.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _gasLimit Gas limit for the interaction.
* @param _calldata Data to send to the OVM_ExecutionManager (encoded with sighash).
* @return _returndata Data sent back by the OVM_ExecutionManager.
*/
function _safeExecutionManagerInteraction(
address _ovmExecutionManager,
uint256 _gasLimit,
bytes memory _calldata
)
private
returns (
bytes memory _returndata
)
{
(
bool success,
bytes memory returndata
) = _ovmExecutionManager.call{gas: _gasLimit}(_calldata);
if (success == false) {
assembly {
revert(add(returndata, 0x20), mload(returndata))
}
} else if (returndata.length == 1) {
assembly {
return(0, 1)
}
} else {
return returndata;
}
}
function _safeExecutionManagerInteraction(
address _ovmExecutionManager,
bytes memory _calldata
)
private
returns (
bytes memory _returndata
)
{
return _safeExecutionManagerInteraction(
_ovmExecutionManager,
gasleft(),
_calldata
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContractAccount.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_ECDSAUtils } from "../../libraries/utils/Lib_ECDSAUtils.sol";
import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol";
/**
* @title mockOVM_ECDSAContractAccount
*/
contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
/********************
* Public Functions *
********************/
/**
* Executes a signed transaction.
* @param _transaction Signed EOA transaction.
* @param _signatureType Hashing scheme used for the transaction (e.g., ETH signed message).
* @param _v Signature `v` parameter.
* @param _r Signature `r` parameter.
* @param _s Signature `s` parameter.
* @return _success Whether or not the call returned (rather than reverted).
* @return _returndata Data returned by the call.
*/
function execute(
bytes memory _transaction,
Lib_OVMCodec.EOASignatureType _signatureType,
uint8 _v,
bytes32 _r,
bytes32 _s
)
override
public
returns (
bool _success,
bytes memory _returndata
)
{
address ovmExecutionManager = msg.sender;
// Skip signature validation in this mock.
Lib_OVMCodec.EOATransaction memory decodedTx = Lib_OVMCodec.decodeEOATransaction(_transaction);
// Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.target == address(0)) {
address created = Lib_SafeExecutionManagerWrapper.safeCREATE(
ovmExecutionManager,
decodedTx.gasLimit,
decodedTx.data
);
// EVM doesn't tell us whether a contract creation failed, even if it reverted during
// initialization. Always return `true` for our success value here.
return (true, abi.encode(created));
} else {
return Lib_SafeExecutionManagerWrapper.safeCALL(
ovmExecutionManager,
decodedTx.gasLimit,
decodedTx.target,
decodedTx.data
);
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Contract Imports */
import { OVM_BaseCrossDomainMessenger } from "../../OVM/bridge/OVM_BaseCrossDomainMessenger.sol";
/**
* @title mockOVM_CrossDomainMessenger
*/
contract mockOVM_CrossDomainMessenger is OVM_BaseCrossDomainMessenger {
/***********
* Structs *
***********/
struct ReceivedMessage {
uint256 timestamp;
address target;
address sender;
bytes message;
uint256 messageNonce;
uint256 gasLimit;
}
/**********************
* Contract Variables *
**********************/
ReceivedMessage[] internal fullReceivedMessages;
address internal targetMessengerAddress;
uint256 internal lastRelayedMessage;
uint256 internal delay;
/***************
* Constructor *
***************/
/**
* @param _delay Time in seconds before a message can be relayed.
*/
constructor(
uint256 _delay
) {
delay = _delay;
}
/********************
* Public Functions *
********************/
/**
* Sets the target messenger address.
* @dev Currently, this function is public and therefore allows anyone to modify the target
* messenger for a given xdomain messenger contract. Obviously this shouldn't be allowed,
* but we still need to determine an adequate mechanism for updating this address.
* @param _targetMessengerAddress New messenger address.
*/
function setTargetMessengerAddress(
address _targetMessengerAddress
)
public
{
targetMessengerAddress = _targetMessengerAddress;
}
/**
* Sends a message to another mock xdomain messenger.
* @param _target Target for the message.
* @param _message Message to send.
* @param _gasLimit Amount of gas to send with the call.
*/
function sendMessage(
address _target,
bytes memory _message,
uint32 _gasLimit
)
public
{
mockOVM_CrossDomainMessenger targetMessenger = mockOVM_CrossDomainMessenger(
targetMessengerAddress
);
// Just send it over!
targetMessenger.receiveMessage(ReceivedMessage({
timestamp: block.timestamp,
target: _target,
sender: msg.sender,
message: _message,
messageNonce: messageNonce,
gasLimit: _gasLimit
}));
messageNonce += 1;
}
/**
* Receives a message to be sent later.
* @param _message Message to send later.
*/
function receiveMessage(
ReceivedMessage memory _message
)
public
{
fullReceivedMessages.push(_message);
}
/**
* Checks whether we have messages to relay.
* @param _exists Whether or not we have more messages to relay.
*/
function hasNextMessage()
public
view
returns (
bool _exists
)
{
return fullReceivedMessages.length < lastRelayedMessage;
}
/**
* Relays the last received message not yet relayed.
*/
function relayNextMessage()
public
{
if (hasNextMessage() == false) {
return;
}
ReceivedMessage memory nextMessage = fullReceivedMessages[lastRelayedMessage];
if (nextMessage.timestamp + delay > block.timestamp) {
return;
}
xDomainMessageSender = nextMessage.sender;
nextMessage.target.call{gas: nextMessage.gasLimit}(nextMessage.message);
lastRelayedMessage += 1;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Proxy Imports */
import { Proxy_Manager } from "./Proxy_Manager.sol";
/**
* @title Proxy_Forwarder
*/
contract Proxy_Forwarder {
Proxy_Manager private proxyManager;
constructor(
address _proxyManager
) {
proxyManager = Proxy_Manager(_proxyManager);
}
fallback()
external
{
address target = _getTarget();
bytes memory data = msg.data;
require(
target != address(0),
"Proxy does not have a target."
);
assembly {
let success := call(
gas(),
target,
0,
add(data, 0x20),
mload(data),
0,
0
)
let size := returndatasize()
let returndata := mload(0x40)
mstore(0x40, add(returndata, add(size, 0x20)))
returndatacopy(add(returndata, 0x20), 0, size)
if iszero(success) {
revert(add(returndata, 0x20), size)
}
return(add(returndata, 0x20), size)
}
}
function _getTarget()
internal
view
returns (
address _target
)
{
address target;
if (proxyManager.isProxy(msg.sender)) {
target = proxyManager.getTarget(msg.sender);
} else if (proxyManager.hasProxy(msg.sender)) {
target = proxyManager.getProxy(msg.sender);
} else {
target = proxyManager.getTarget(address(this));
}
return target;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/**
* @title Proxy_Manager
*/
contract Proxy_Manager {
mapping (bytes32 => address) private proxyByName;
mapping (bytes32 => address) private targetByName;
mapping (address => bytes32) private nameByProxy;
mapping (address => bytes32) private nameByTarget;
function setProxy(
string memory _name,
address _proxy
)
public
{
proxyByName[_getNameHash(_name)] = _proxy;
nameByProxy[_proxy] = _getNameHash(_name);
}
function getProxy(
string memory _name
)
public
view
returns (
address _proxy
)
{
return proxyByName[_getNameHash(_name)];
}
function getProxy(
address _target
)
public
view
returns (
address _proxy
)
{
return proxyByName[nameByTarget[_target]];
}
function hasProxy(
string memory _name
)
public
view
returns (
bool _hasProxy
)
{
return getProxy(_name) != address(0);
}
function hasProxy(
address _target
)
public
view
returns (
bool _hasProxy
)
{
return getProxy(_target) != address(0);
}
function isProxy(
address _proxy
)
public
view
returns (
bool _isProxy
)
{
return nameByProxy[_proxy] != bytes32('');
}
function setTarget(
string memory _name,
address _target
)
public
{
targetByName[_getNameHash(_name)] = _target;
nameByTarget[_target] = _getNameHash(_name);
}
function getTarget(
string memory _name
)
public
view
returns (
address _target
)
{
return targetByName[_getNameHash(_name)];
}
function getTarget(
address _proxy
)
public
view
returns (
address _target
)
{
return targetByName[nameByProxy[_proxy]];
}
function hasTarget(
string memory _name
)
public
view
returns (
bool _hasTarget
)
{
return getTarget(_name) != address(0);
}
function hasTarget(
address _proxy
)
public
view
returns (
bool _hasTarget
)
{
return getTarget(_proxy) != address(0);
}
function isTarget(
address _target
)
public
view
returns (
bool _isTarget
)
{
return nameByTarget[_target] != bytes32('');
}
function _getNameHash(
string memory _name
)
internal
pure
returns (
bytes32 _hash
)
{
return keccak256(abi.encodePacked(_name));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Proxy Imports */
import { Proxy_Manager } from "./Proxy_Manager.sol";
/**
* @title Proxy_Resolver
*/
contract Proxy_Resolver {
Proxy_Manager internal proxyManager;
constructor(
address _proxyManager
) {
proxyManager = Proxy_Manager(_proxyManager);
}
function resolve(
string memory _name
)
public
view
returns (
address _proxy
)
{
return proxyManager.getProxy(_name);
}
function resolveTarget(
string memory _name
)
public
view
returns (
address _target
)
{
return proxyManager.getTarget(_name);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
contract Helper_SimpleProxy {
address private owner;
address private target;
constructor() {
owner = msg.sender;
}
fallback()
external
{
makeExternalCall(target, msg.data);
}
function setTarget(
address _target
)
public
{
if (msg.sender == owner) {
target = _target;
} else {
makeExternalCall(target, msg.data);
}
}
function makeExternalCall(
address _target,
bytes memory _calldata
)
private
{
(bool success, bytes memory returndata) = _target.call(_calldata);
if (success) {
assembly {
return(add(returndata, 0x20), mload(returndata))
}
} else {
assembly {
revert(add(returndata, 0x20), mload(returndata))
}
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol";
/**
* @title TestLib_OVMCodec
*/
contract TestLib_OVMCodec {
function decodeEOATransaction(
bytes memory _transaction
)
public
pure
returns (
Lib_OVMCodec.EOATransaction memory _decoded
)
{
return Lib_OVMCodec.decodeEOATransaction(_transaction);
}
function encodeTransaction(
Lib_OVMCodec.Transaction memory _transaction
)
public
pure
returns (
bytes memory _encoded
)
{
return Lib_OVMCodec.encodeTransaction(_transaction);
}
function hashTransaction(
Lib_OVMCodec.Transaction memory _transaction
)
public
pure
returns (
bytes32 _hash
)
{
return Lib_OVMCodec.hashTransaction(_transaction);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_RLPReader } from "../../optimistic-ethereum/libraries/rlp/Lib_RLPReader.sol";
/**
* @title TestLib_RLPReader
*/
contract TestLib_RLPReader {
function readList(
bytes memory _in
)
public
view
returns (
bytes[] memory
)
{
Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_in);
bytes[] memory out = new bytes[](decoded.length);
for (uint256 i = 0; i < out.length; i++) {
out[i] = Lib_RLPReader.readRawBytes(decoded[i]);
}
return out;
}
function readString(
bytes memory _in
)
public
view
returns (
string memory
)
{
return Lib_RLPReader.readString(_in);
}
function readBytes(
bytes memory _in
)
public
view
returns (
bytes memory
)
{
return Lib_RLPReader.readBytes(_in);
}
function readBytes32(
bytes memory _in
)
public
view
returns (
bytes32
)
{
return Lib_RLPReader.readBytes32(_in);
}
function readUint256(
bytes memory _in
)
public
view
returns (
uint256
)
{
return Lib_RLPReader.readUint256(_in);
}
function readBool(
bytes memory _in
)
public
view
returns (
bool
)
{
return Lib_RLPReader.readBool(_in);
}
function readAddress(
bytes memory _in
)
public
view
returns (
address
)
{
return Lib_RLPReader.readAddress(_in);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_RLPWriter } from "../../optimistic-ethereum/libraries/rlp/Lib_RLPWriter.sol";
/**
* @title TestLib_RLPWriter
*/
contract TestLib_RLPWriter {
function writeBytes(
bytes memory _in
)
public
pure
returns (
bytes memory _out
)
{
return Lib_RLPWriter.writeBytes(_in);
}
function writeList(
bytes[] memory _in
)
public
pure
returns (
bytes memory _out
)
{
return Lib_RLPWriter.writeList(_in);
}
function writeString(
string memory _in
)
public
pure
returns (
bytes memory _out
)
{
return Lib_RLPWriter.writeString(_in);
}
function writeAddress(
address _in
)
public
pure
returns (
bytes memory _out
)
{
return Lib_RLPWriter.writeAddress(_in);
}
function writeUint(
uint _in
)
public
pure
returns (
bytes memory _out
)
{
return Lib_RLPWriter.writeUint(_in);
}
function writeInt(
int _in
)
public
pure
returns (
bytes memory _out
)
{
return Lib_RLPWriter.writeInt(_in);
}
function writeBool(
bool _in
)
public
pure
returns (
bytes memory _out
)
{
return Lib_RLPWriter.writeBool(_in);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_MerkleTrie } from "../../optimistic-ethereum/libraries/trie/Lib_MerkleTrie.sol";
/**
* @title TestLib_MerkleTrie
*/
contract TestLib_MerkleTrie {
function verifyInclusionProof(
bytes memory _key,
bytes memory _value,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bool
)
{
return Lib_MerkleTrie.verifyInclusionProof(
_key,
_value,
_proof,
_root
);
}
function verifyExclusionProof(
bytes memory _key,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bool
)
{
return Lib_MerkleTrie.verifyExclusionProof(
_key,
_proof,
_root
);
}
function update(
bytes memory _key,
bytes memory _value,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bytes32
)
{
return Lib_MerkleTrie.update(
_key,
_value,
_proof,
_root
);
}
function get(
bytes memory _key,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bool,
bytes memory
)
{
return Lib_MerkleTrie.get(
_key,
_proof,
_root
);
}
function getSingleNodeRootHash(
bytes memory _key,
bytes memory _value
)
public
view
returns (
bytes32
)
{
return Lib_MerkleTrie.getSingleNodeRootHash(
_key,
_value
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_SecureMerkleTrie } from "../../optimistic-ethereum/libraries/trie/Lib_SecureMerkleTrie.sol";
/**
* @title TestLib_SecureMerkleTrie
*/
contract TestLib_SecureMerkleTrie {
function verifyInclusionProof(
bytes memory _key,
bytes memory _value,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bool
)
{
return Lib_SecureMerkleTrie.verifyInclusionProof(
_key,
_value,
_proof,
_root
);
}
function verifyExclusionProof(
bytes memory _key,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bool
)
{
return Lib_SecureMerkleTrie.verifyExclusionProof(
_key,
_proof,
_root
);
}
function update(
bytes memory _key,
bytes memory _value,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bytes32
)
{
return Lib_SecureMerkleTrie.update(
_key,
_value,
_proof,
_root
);
}
function get(
bytes memory _key,
bytes memory _proof,
bytes32 _root
)
public
view
returns (
bool,
bytes memory
)
{
return Lib_SecureMerkleTrie.get(
_key,
_proof,
_root
);
}
function getSingleNodeRootHash(
bytes memory _key,
bytes memory _value
)
public
view
returns (
bytes32
)
{
return Lib_SecureMerkleTrie.getSingleNodeRootHash(
_key,
_value
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_Bytes32Utils } from "../../optimistic-ethereum/libraries/utils/Lib_Bytes32Utils.sol";
/**
* @title TestLib_Byte32Utils
*/
contract TestLib_Bytes32Utils {
function toBool(
bytes32 _in
)
public
pure
returns (
bool _out
)
{
return Lib_Bytes32Utils.toBool(_in);
}
function fromBool(
bool _in
)
public
pure
returns (
bytes32 _out
)
{
return Lib_Bytes32Utils.fromBool(_in);
}
function toAddress(
bytes32 _in
)
public
pure
returns (
address _out
)
{
return Lib_Bytes32Utils.toAddress(_in);
}
function fromAddress(
address _in
)
public
pure
returns (
bytes32 _out
)
{
return Lib_Bytes32Utils.fromAddress(_in);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_BytesUtils } from "../../optimistic-ethereum/libraries/utils/Lib_BytesUtils.sol";
/**
* @title TestLib_BytesUtils
*/
contract TestLib_BytesUtils {
function concat(
bytes memory _preBytes,
bytes memory _postBytes
)
public
pure
returns (bytes memory)
{
return Lib_BytesUtils.concat(
_preBytes,
_postBytes
);
}
function slice(
bytes memory _bytes,
uint256 _start,
uint256 _length
)
public
pure
returns (bytes memory)
{
return Lib_BytesUtils.slice(
_bytes,
_start,
_length
);
}
function toBytes32(
bytes memory _bytes
)
public
pure
returns (bytes32)
{
return Lib_BytesUtils.toBytes32(
_bytes
);
}
function toUint256(
bytes memory _bytes
)
public
pure
returns (uint256)
{
return Lib_BytesUtils.toUint256(
_bytes
);
}
function toNibbles(
bytes memory _bytes
)
public
pure
returns (bytes memory)
{
return Lib_BytesUtils.toNibbles(
_bytes
);
}
function fromNibbles(
bytes memory _bytes
)
public
pure
returns (bytes memory)
{
return Lib_BytesUtils.fromNibbles(
_bytes
);
}
function equal(
bytes memory _bytes,
bytes memory _other
)
public
pure
returns (bool)
{
return Lib_BytesUtils.equal(
_bytes,
_other
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_ECDSAUtils } from "../../optimistic-ethereum/libraries/utils/Lib_ECDSAUtils.sol";
/**
* @title TestLib_ECDSAUtils
*/
contract TestLib_ECDSAUtils {
function recover(
bytes memory _message,
bool _isEthSignedMessage,
uint8 _v,
bytes32 _r,
bytes32 _s,
uint256 _chainId
)
public
pure
returns (
address _sender
)
{
return Lib_ECDSAUtils.recover(
_message,
_isEthSignedMessage,
_v,
_r,
_s,
_chainId
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_EthUtils } from "../../optimistic-ethereum/libraries/utils/Lib_EthUtils.sol";
/**
* @title TestLib_EthUtils
*/
contract TestLib_EthUtils {
function getCode(
address _address,
uint256 _offset,
uint256 _length
)
public
view
returns (
bytes memory _code
)
{
return Lib_EthUtils.getCode(
_address,
_offset,
_length
);
}
function getCode(
address _address
)
public
view
returns (
bytes memory _code
)
{
return Lib_EthUtils.getCode(
_address
);
}
function getCodeSize(
address _address
)
public
view
returns (
uint256 _codeSize
)
{
return Lib_EthUtils.getCodeSize(
_address
);
}
function getCodeHash(
address _address
)
public
view
returns (
bytes32 _codeHash
)
{
return Lib_EthUtils.getCodeHash(
_address
);
}
function createContract(
bytes memory _code
)
public
returns (
address _created
)
{
return Lib_EthUtils.createContract(
_code
);
}
function getAddressForCREATE(
address _creator,
uint256 _nonce
)
public
view
returns (
address _address
)
{
return Lib_EthUtils.getAddressForCREATE(
_creator,
_nonce
);
}
function getAddressForCREATE2(
address _creator,
bytes memory _bytecode,
bytes32 _salt
)
public
view
returns (address _address)
{
return Lib_EthUtils.getAddressForCREATE2(
_creator,
_bytecode,
_salt
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_MerkleUtils } from "../../optimistic-ethereum/libraries/utils/Lib_MerkleUtils.sol";
/**
* @title TestLib_MerkleUtils
*/
contract TestLib_MerkleUtils {
function getMerkleRoot(
bytes32[] memory _hashes
)
public
view
returns (
bytes32 _root
)
{
return Lib_MerkleUtils.getMerkleRoot(
_hashes
);
}
function getMerkleRoot(
bytes[] memory _elements
)
public
view
returns (
bytes32 _root
)
{
return Lib_MerkleUtils.getMerkleRoot(
_elements
);
}
function verify(
bytes32 _root,
bytes memory _leaf,
uint256 _path,
bytes32[] memory _siblings
)
public
pure
returns (
bool _verified
)
{
return Lib_MerkleUtils.verify(
_root,
_leaf,
_path,
_siblings
);
}
}
// 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
)
public
{
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
# Execution Managager Integration/State Tests
## General notes
- run everything below with invalid state accesses automatically and assert invalid state access handled in ALL cases
- run everything below through a state manager proxy which consumes a different amount of gas and check that the **OVM** gas values are not different
## Test Cases
- CALL-types
- for all: call an undeployed contract and make sure it errors or whatevs (or maybe that's just a unit test)
ALL DONE - ovmCALL
- -> ovmCALLER
- -> ovmADDRESS
- -> SLOAD
- -> SSTORE
- -> CREATE/2
ALL DONE - ovmSTATICCALL
- -> ovmCALLER
- -> ovmADDRESS
- -> SLOAD
- -> SSTORE (fail)
- -> CREATE/2 (fail)
- -> ovmCALL -> ovmSSTORE
- -> ovmCALL -> ovmCREATE
- -> ovmSTATICCALL -> RETURN -> SSTORE (fails)
- ovmDELEGATECALL
DONE - -> ovmCALLER
DONE - -> ovmADDRESS
DONE - -> SLOAD
DONE - -> SSTORE
DONE - -> CREATE/2
DONE - -> ovmDELEGATECALL -> ovmCALLER
DONE - -> ovmDELEGATECALL -> ovmADDRESS
DONE - -> ovmDELEGATECALL -> ovmCREATE
- Code-related
- TODO: fill this in
- CREATE-types
- do we just duplicate these exactly for CREATE and CREATE2? Probably
DONE - ovmCREATE -> success -> ovmEXTCODE{SIZE,HASH,COPY}
DONE - ovmCREATE -> fail (ovmREVERT, NOT out of gas/invalid jump) -> ovmEXTCODE{SIZE,HASH,COPY}
- ovmCREATE -> fail -> ovmCALL what was attempted to be created (fail)
- ovmCREATE -> ovmCREATE (during constructor) -> success -> success (check right address for inner deployment)
DONE - ovmCREATE -> ovmCALL(in constructor) -> ovmSSTORE, return -> ovmREVERT (deployment fails, storage not modified, but state access gas correctly increased)
DONE - ovmCREATE -> INVALID -> returns 0 address
- ovmCREATE -> ovmCREATE (during constructor) -> success -> fail (outer contract)
- "creator" does ovmCREATE -> invalid jumpdest -> creator out-of-gasses (or at least, appears to--really it will revert with no data, so there will be some gas left)
- "creator" does ovmCREATE -> initcode does ovmCREATE -> invalid jumpdest -> creator out-of-gasses (or at least, appears to--really it will revert with no data, so there will be some gas left) AKA same as above but nested CREATEs
- CREATE collisions: TODO fill in what this needs
- OVM gas metering
- do everything for both queue origins/flip flopped roles:
- blocks transactions whose gas limit would put the cumulative gas over the max for the current epoch
- starts a new epoch and allows tx through if the above would have held true, but new epoch has begun
- allows transaction through queue B even if queue A cumulative gas would have blocked it
- out of gas
- ovmCALL -> [ovmCALL(gas()/2) -> out of gas] -> SSTORE (does not out of gas parent)
- ovmCREATE -> ovmCALL(in constructor) -> ovmSSTORE, return -> ovmREVERT (deployment fails, storage not modified, but state access gas correctly increased)
- State access limiting logic
- ovmCALL(gas()/2) -> ovmCALL(gas()) -> out of gas -> return(someVal) -> SSTORE(someVal)
- this one shows that if a subcall out-of-gasses, but you do not do any further state accesses, then you can still return, and if your parent has a bigger allocation left, they can still ovmSSTORE
- ovmSSTORE, repeated max times, ovmCALL(gas()) -> ovmSSTORE -> fails (even though max gas allotment was given, the parent already used them up)
- ovmCALL(gas/2) -> ovmCREATE, out of gas -> SSTORE succeeds
- ovmCALL(gas) -> ovmCREATE, out of gas -> SSTORE fails
- ovmCALL(gas) -> ovmCREATE, ovmREVERT (in init) -> SSTORE succeeds
- ovmCALL(gas) -> ovmCREATE, ovmSSTORE(max times), ovmREVERT -> ovmSSTORE fails (max allocated in reverting CREATE)
- ovmCALL(gas) -> ovmCREATE -> ovmCREATE ovmSSTORE(max times), ovmREVERT -> deploy -> ovmSSTORE fails (propogates through a failed CREATE inside a successful CREATE
- ovmCALL(gas) -> ovmCREATE -> ovmCREATE, ovmSLOAD(max times), inner deploy success -> outer deploy fail -> ovmSSTORE fails (propogates through a successful create inside a failed create)
- ovmCREATE -> ovmCALL, ovmSSTORE (max), return -> ovmSSTORE fails
- ovmCREATE -> ovmCALL(gas/2) -> ovmCREATE, out of gas, call reverts (as if out of gas) -> ovmSSTORE (success in constructor)
- Explicit invalid state access tests
- CALL -> CALL, ISA
- CALL -> CALL, CALL, ISA
DONE - CREATE -> CREATE, ISA
- CREATE -> CREATE -> CREATE ISA
DONE - CREATE -> CALL, ISA
DONE - CALL -> CREATE, ISA
- CALL -> CREATE -> CALL, ISA
- CREATE -> CALL -> CREATE, ISA
\ No newline at end of file
{
"name": "optimism",
"version": "1.0.0",
"main": "index.js",
"name": "@eth-optimism/contracts",
"version": "0.0.2-alpha.1",
"main": "build/src/index.js",
"files": [
"build/**/*.js",
"build/contracts/*",
"build/dumps/*json",
"build/artifacts/*json"
],
"license": "MIT",
"scripts": {
"build": "yarn run build:contracts",
"build": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy && yarn run build:dump",
"build:typescript": "tsc -p .",
"build:contracts": "buidler compile",
"build:dump": "ts-node \"bin/take-dump.ts\"",
"build:copy": "copyfiles -u 2 \"contracts/optimistic-ethereum/**/*.sol\" \"build/contracts\"",
"test": "yarn run test:contracts",
"test:contracts": "buidler test \"test/contracts/OVM/execution/OVM_ExecutionManager/run.spec.ts\"",
"lint": "tslint --format stylish --project .",
"fix": "prettier --config prettier-config.json --write \"buidler.config.ts\" \"{src,test}/**/*.ts\""
"test:contracts": "buidler test --show-stack-traces",
"lint": "yarn run lint:typescript",
"lint:typescript": "tslint --format stylish --project .",
"lint:fix": "yarn run lint:fix:typescript",
"lint:fix:typescript": "prettier --config prettier-config.json --write \"buidler.config.ts\" \"{src,test}/**/*.ts\"",
"clean": "rm -rf ./artifacts ./build ./cache"
},
"dependencies": {
"ethers": "5.0.0"
},
"devDependencies": {
"@eth-optimism/smock": "^0.0.2",
"@nomiclabs/buidler": "^1.4.4",
"@nomiclabs/buidler-ethers": "^2.0.0",
"@nomiclabs/buidler-waffle": "^2.0.0",
"@types/chai": "^4.2.12",
"@types/lodash": "^4.14.161",
"@types/mocha": "^8.0.3",
"@types/node": "^14.6.0",
"assert": "^2.0.0",
"buffer-xor": "^2.0.2",
"chai": "^4.2.0",
"copyfiles": "^2.3.0",
"ethereum-waffle": "3.0.0",
"ethers": "5.0.0",
"fs-extra": "^9.0.1",
"ganache-core": "^2.12.1",
"lodash": "^4.17.20",
"merkle-patricia-tree": "^4.0.0",
"mkdirp": "^1.0.4",
"mocha": "^8.1.1",
"prettier": "^2.1.2",
"random-bytes-seed": "^1.0.3",
"rlp": "^2.2.6",
"seedrandom": "^3.0.5",
"ts-node": "^9.0.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
......
import * as path from 'path'
import { ethers, ContractFactory, Signer } from 'ethers'
import { Interface } from 'ethers/lib/utils'
export const getContractDefinition = (name: string): any => {
return require(path.join(__dirname, '../artifacts', `${name}.json`))
}
export const getContractInterface = (name: string): Interface => {
const definition = getContractDefinition(name)
return new ethers.utils.Interface(definition.abi)
}
export const getContractFactory = (
name: string,
signer?: Signer
): ContractFactory => {
const definition = getContractDefinition(name)
const contractInterface = getContractInterface(name)
return new ContractFactory(contractInterface, definition.bytecode, signer)
}
/* External Imports */
import { Signer, ContractFactory, Contract } from 'ethers'
/* Internal Imports */
import { getContractFactory } from '../contract-defs'
export interface RollupDeployConfig {
deploymentSigner: Signer
ovmGasMeteringConfig: {
minTransactionGasLimit: number
maxTransactionGasLimit: number
maxGasPerQueuePerEpoch: number
secondsPerEpoch: number
}
ovmGlobalContext: {
ovmCHAINID: number
}
transactionChainConfig: {
sequencer: string | Signer
forceInclusionPeriodSeconds: number
}
whitelistConfig: {
owner: string | Signer
allowArbitraryContractDeployment: boolean
}
dependencies?: string[]
}
export interface ContractDeployParameters {
factory: ContractFactory
params?: any[]
afterDeploy?: (contracts?: { [name: string]: Contract }) => Promise<void>
}
export interface ContractDeployConfig {
[name: string]: ContractDeployParameters
}
export const makeContractDeployConfig = async (
config: RollupDeployConfig,
AddressManager: Contract
): Promise<ContractDeployConfig> => {
return {
OVM_L1CrossDomainMessenger: {
factory: getContractFactory('OVM_L1CrossDomainMessenger'),
params: [AddressManager.address],
},
OVM_L2CrossDomainMessenger: {
factory: getContractFactory('OVM_L2CrossDomainMessenger'),
params: [AddressManager.address],
},
OVM_CanonicalTransactionChain: {
factory: getContractFactory('OVM_CanonicalTransactionChain'),
params: [
AddressManager.address,
config.transactionChainConfig.forceInclusionPeriodSeconds,
],
afterDeploy: async (): Promise<void> => {
const sequencer = config.transactionChainConfig.sequencer
const sequencerAddress =
typeof sequencer === 'string'
? sequencer
: await sequencer.getAddress()
await AddressManager.setAddress('Sequencer', sequencerAddress)
},
},
OVM_StateCommitmentChain: {
factory: getContractFactory('OVM_StateCommitmentChain'),
params: [AddressManager.address],
},
OVM_DeployerWhitelist: {
factory: getContractFactory('OVM_DeployerWhitelist'),
params: [],
},
OVM_L1MessageSender: {
factory: getContractFactory('OVM_L1MessageSender'),
params: [],
},
OVM_L2ToL1MessagePasser: {
factory: getContractFactory('OVM_L2ToL1MessagePasser'),
params: [],
},
OVM_SafetyChecker: {
factory: getContractFactory('OVM_SafetyChecker'),
params: [],
},
OVM_ExecutionManager: {
factory: getContractFactory('OVM_ExecutionManager'),
params: [
AddressManager.address,
config.ovmGasMeteringConfig,
config.ovmGlobalContext,
],
},
OVM_StateManager: {
factory: getContractFactory('OVM_StateManager'),
params: [await config.deploymentSigner.getAddress()],
afterDeploy: async (contracts): Promise<void> => {
await contracts.OVM_StateManager.setExecutionManager(
contracts.OVM_ExecutionManager.address
)
},
},
OVM_StateManagerFactory: {
factory: getContractFactory('OVM_StateManagerFactory'),
},
OVM_L1ToL2TransactionQueue: {
factory: getContractFactory('OVM_L1ToL2TransactionQueue'),
params: [AddressManager.address],
},
OVM_FraudVerifier: {
factory: getContractFactory('OVM_FraudVerifier'),
params: [AddressManager.address],
},
OVM_StateTransitionerFactory: {
factory: getContractFactory('OVM_StateTransitionerFactory'),
},
OVM_ECDSAContractAccount: {
factory: getContractFactory('OVM_ECDSAContractAccount'),
},
mockOVM_ECDSAContractAccount: {
factory: getContractFactory('mockOVM_ECDSAContractAccount'),
},
}
}
/* External Imports */
import { Signer, Contract, ContractFactory } from 'ethers'
/* Internal Imports */
import { RollupDeployConfig, makeContractDeployConfig } from './config'
import { getContractFactory } from '../contract-defs'
export interface DeployResult {
AddressManager: Contract
failedDeployments: string[]
contracts: {
[name: string]: Contract
}
}
export const deploy = async (
config: RollupDeployConfig
): Promise<DeployResult> => {
const Factory__SimpleProxy: ContractFactory = getContractFactory(
'Helper_SimpleProxy',
config.deploymentSigner
)
const AddressManager: Contract = await getContractFactory(
'Lib_AddressManager',
config.deploymentSigner
).deploy()
const contractDeployConfig = await makeContractDeployConfig(
config,
AddressManager
)
const failedDeployments: string[] = []
const contracts: {
[name: string]: Contract
} = {}
for (const [name, contractDeployParameters] of Object.entries(
contractDeployConfig
)) {
if (config.dependencies && !config.dependencies.includes(name)) {
continue
}
const SimpleProxy = await Factory__SimpleProxy.deploy()
await AddressManager.setAddress(name, SimpleProxy.address)
contracts[`Proxy__${name}`] = SimpleProxy
try {
contracts[name] = await contractDeployParameters.factory
.connect(config.deploymentSigner)
.deploy(...(contractDeployParameters.params || []))
await SimpleProxy.setTarget(contracts[name].address)
} catch (err) {
failedDeployments.push(name)
}
}
for (const [name, contractDeployParameters] of Object.entries(
contractDeployConfig
)) {
if (config.dependencies && !config.dependencies.includes(name)) {
continue
}
if (contractDeployParameters.afterDeploy) {
await contractDeployParameters.afterDeploy(contracts)
}
}
return {
AddressManager,
failedDeployments,
contracts,
}
}
export { RollupDeployConfig } from './config'
export { deploy } from './deploy'
/* External Imports */
import * as path from 'path'
import { ethers } from 'ethers'
import * as Ganache from 'ganache-core'
import { keccak256 } from 'ethers/lib/utils'
/* Internal Imports */
import { deploy, RollupDeployConfig } from './contract-deployment'
import { fromHexString, toHexString, remove0x } from '../test/helpers/utils'
import { getContractDefinition } from './contract-defs'
interface StorageDump {
[key: string]: string
}
export interface StateDump {
accounts: {
[name: string]: {
address: string
code: string
codeHash: string
storage: StorageDump
abi: any
}
}
}
/**
* Generates a storage dump for a given address.
* @param cStateManager Instance of the callback-based internal vm StateManager.
* @param address Address to generate a state dump for.
*/
const getStorageDump = async (
cStateManager: any,
address: string
): Promise<StorageDump> => {
return new Promise<StorageDump>((resolve, reject) => {
cStateManager._getStorageTrie(address, (err: any, trie: any) => {
if (err) {
reject(err)
}
const storage: StorageDump = {}
const stream = trie.createReadStream()
stream.on('data', (val: any) => {
storage[val.key.toString('hex')] = val.value.toString('hex')
})
stream.on('end', () => {
resolve(storage)
})
})
})
}
/**
* Replaces old addresses found in a storage dump with new ones.
* @param storageDump Storage dump to sanitize.
* @param accounts Set of accounts to sanitize with.
* @returns Sanitized storage dump.
*/
const sanitizeStorageDump = (
storageDump: StorageDump,
accounts: Array<{
originalAddress: string
deadAddress: string
}>
): StorageDump => {
for (const account of accounts) {
account.originalAddress = remove0x(account.originalAddress).toLowerCase()
account.deadAddress = remove0x(account.deadAddress).toLowerCase()
}
for (const [key, value] of Object.entries(storageDump)) {
let parsedKey = key
let parsedValue = value
for (const account of accounts) {
const re = new RegExp(`${account.originalAddress}`, 'g')
parsedValue = parsedValue.replace(re, account.deadAddress)
parsedKey = parsedKey.replace(re, account.deadAddress)
}
if (parsedKey !== key) {
delete storageDump[key]
}
storageDump[parsedKey] = parsedValue
}
return storageDump
}
export const makeStateDump = async (): Promise<any> => {
const ganache = (Ganache as any).provider({
gasLimit: 100_000_000,
allowUnlimitedContractSize: true,
accounts: [
{
secretKey:
'0x29f3edee0ad3abf8e2699402e0e28cd6492c9be7eaab00d732a791c33552f797',
balance: 10000000000000000000000000000000000,
},
],
})
const provider = new ethers.providers.Web3Provider(ganache)
const signer = provider.getSigner(0)
const config: RollupDeployConfig = {
deploymentSigner: signer,
ovmGasMeteringConfig: {
minTransactionGasLimit: 0,
maxTransactionGasLimit: 1_000_000_000,
maxGasPerQueuePerEpoch: 1_000_000_000_000,
secondsPerEpoch: 600,
},
ovmGlobalContext: {
ovmCHAINID: 420,
},
transactionChainConfig: {
sequencer: signer,
forceInclusionPeriodSeconds: 600,
},
whitelistConfig: {
owner: signer,
allowArbitraryContractDeployment: true,
},
dependencies: [
'Lib_AddressManager',
'OVM_DeployerWhitelist',
'OVM_L1MessageSender',
'OVM_L2ToL1MessagePasser',
'OVM_L2CrossDomainMessenger',
'OVM_SafetyChecker',
'OVM_ExecutionManager',
'OVM_StateManager',
'mockOVM_ECDSAContractAccount',
],
}
const precompiles = {
OVM_L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000',
OVM_L1MessageSender: '0x4200000000000000000000000000000000000001',
OVM_DeployerWhitelist: '0x4200000000000000000000000000000000000002',
}
const deploymentResult = await deploy(config)
deploymentResult.contracts['Lib_AddressManager'] =
deploymentResult.AddressManager
if (deploymentResult.failedDeployments.length > 0) {
throw new Error(
`Could not generate state dump, deploy failed for: ${deploymentResult.failedDeployments}`
)
}
const pStateManager = ganache.engine.manager.state.blockchain.vm.pStateManager
const cStateManager = pStateManager._wrapped
const dump: StateDump = {
accounts: {},
}
for (let i = 0; i < Object.keys(deploymentResult.contracts).length; i++) {
const name = Object.keys(deploymentResult.contracts)[i]
const contract = deploymentResult.contracts[name]
const codeBuf = await pStateManager.getContractCode(
fromHexString(contract.address)
)
const code = toHexString(codeBuf)
const deadAddress =
precompiles[name] ||
`0xdeaddeaddeaddeaddeaddeaddeaddeaddead${i.toString(16).padStart(4, '0')}`
dump.accounts[name] = {
address: deadAddress,
code,
codeHash: keccak256(code),
storage: await getStorageDump(cStateManager, contract.address),
abi: getContractDefinition(name.replace('Proxy__', '')).abi,
}
}
const addressMap = Object.keys(dump.accounts).map((name) => {
return {
originalAddress: deploymentResult.contracts[name].address,
deadAddress: dump.accounts[name].address,
}
})
for (const name of Object.keys(dump.accounts)) {
dump.accounts[name].storage = sanitizeStorageDump(
dump.accounts[name].storage,
addressMap
)
}
return dump
}
export const getLatestStateDump = (): StateDump => {
return require(path.join(__dirname, '../dumps', `state-dump.latest.json`))
}
export * from './contract-defs'
export { getLatestStateDump, StateDump } from './contract-dumps'
export * from './contract-deployment'
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
/* Internal Imports */
import {
makeAddressManager,
setProxyTarget,
NON_NULL_BYTES32,
ZERO_ADDRESS,
NON_ZERO_ADDRESS,
NULL_BYTES32,
DUMMY_BATCH_HEADERS,
DUMMY_BATCH_PROOFS,
TrieTestGenerator,
toHexString,
} from '../../../helpers'
import { getContractInterface } from '../../../../src'
import { keccak256 } from 'ethers/lib/utils'
const getXDomainCalldata = (
sender: string,
target: string,
message: string,
messageNonce: number
): string => {
return getContractInterface(
'OVM_L2CrossDomainMessenger'
).encodeFunctionData('relayMessage', [target, sender, message, messageNonce])
}
describe('OVM_L1CrossDomainMessenger', () => {
let signer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
})
let AddressManager: Contract
before(async () => {
AddressManager = await makeAddressManager()
})
let Mock__TargetContract: MockContract
let Mock__OVM_L2CrossDomainMessenger: MockContract
let Mock__OVM_CanonicalTransactionChain: MockContract
let Mock__OVM_StateCommitmentChain: MockContract
before(async () => {
Mock__TargetContract = smockit(
await ethers.getContractFactory('Helper_SimpleProxy')
)
Mock__OVM_L2CrossDomainMessenger = smockit(
await ethers.getContractFactory('OVM_L2CrossDomainMessenger')
)
Mock__OVM_CanonicalTransactionChain = smockit(
await ethers.getContractFactory('OVM_CanonicalTransactionChain')
)
Mock__OVM_StateCommitmentChain = smockit(
await ethers.getContractFactory('OVM_StateCommitmentChain')
)
await AddressManager.setAddress(
'OVM_L2CrossDomainMessenger',
Mock__OVM_L2CrossDomainMessenger.address
)
await setProxyTarget(
AddressManager,
'OVM_CanonicalTransactionChain',
Mock__OVM_CanonicalTransactionChain
)
await setProxyTarget(
AddressManager,
'OVM_StateCommitmentChain',
Mock__OVM_StateCommitmentChain
)
})
let Factory__OVM_L1CrossDomainMessenger: ContractFactory
before(async () => {
Factory__OVM_L1CrossDomainMessenger = await ethers.getContractFactory(
'OVM_L1CrossDomainMessenger'
)
})
let OVM_L1CrossDomainMessenger: Contract
beforeEach(async () => {
OVM_L1CrossDomainMessenger = await Factory__OVM_L1CrossDomainMessenger.deploy(
AddressManager.address
)
})
describe('sendMessage', () => {
const target = NON_ZERO_ADDRESS
const message = NON_NULL_BYTES32
const gasLimit = 100_000
it('should be able to send a single message', async () => {
await expect(
OVM_L1CrossDomainMessenger.sendMessage(target, message, gasLimit)
).to.not.be.reverted
expect(
Mock__OVM_CanonicalTransactionChain.smocked.enqueue.calls[0]
).to.deep.equal([
Mock__OVM_L2CrossDomainMessenger.address,
BigNumber.from(gasLimit),
getXDomainCalldata(await signer.getAddress(), target, message, 0),
])
})
it('should be able to send the same message twice', async () => {
await OVM_L1CrossDomainMessenger.sendMessage(target, message, gasLimit)
await expect(
OVM_L1CrossDomainMessenger.sendMessage(target, message, gasLimit)
).to.not.be.reverted
})
})
describe('replayMessage', () => {
const target = NON_ZERO_ADDRESS
const message = NON_NULL_BYTES32
const gasLimit = 100_000
it('should revert if the message does not exist', async () => {
await expect(
OVM_L1CrossDomainMessenger.replayMessage(
target,
await signer.getAddress(),
message,
0,
gasLimit
)
).to.be.revertedWith('Provided message has not already been sent.')
})
it('should succeed if the message exists', async () => {
await OVM_L1CrossDomainMessenger.sendMessage(target, message, gasLimit)
await expect(
OVM_L1CrossDomainMessenger.replayMessage(
target,
await signer.getAddress(),
message,
0,
gasLimit
)
).to.not.be.reverted
})
})
describe('relayMessage', () => {
let target: string
let message: string
let sender: string
let proof: any
before(async () => {
target = Mock__TargetContract.address
message = Mock__TargetContract.interface.encodeFunctionData('setTarget', [
NON_ZERO_ADDRESS,
])
sender = await signer.getAddress()
const calldata = getXDomainCalldata(sender, target, message, 0)
const precompile = '0x4200000000000000000000000000000000000000'
const storageKey = keccak256(keccak256(calldata) + '00'.repeat(32))
const storageGenerator = await TrieTestGenerator.fromNodes({
nodes: [
{
key: storageKey,
val: '0x' + '01'.padStart(64, '0'),
},
],
secure: true,
})
const generator = await TrieTestGenerator.fromAccounts({
accounts: [
{
address: precompile,
nonce: 0,
balance: 0,
codeHash: keccak256('0x1234'),
storageRoot: toHexString(storageGenerator._trie.root),
},
],
secure: true,
})
proof = {
stateRoot: toHexString(generator._trie.root),
stateRootBatchHeader: DUMMY_BATCH_HEADERS[0],
stateRootProof: DUMMY_BATCH_PROOFS[0],
stateTrieWitness: (await generator.makeAccountProofTest(precompile))
.accountTrieWitness,
storageTrieWitness: (
await storageGenerator.makeInclusionProofTest(storageKey)
).proof,
}
})
beforeEach(async () => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
true
)
Mock__OVM_StateCommitmentChain.smocked.insideFraudProofWindow.will.return.with(
false
)
})
it('should revert if still inside the fraud proof window', async () => {
Mock__OVM_StateCommitmentChain.smocked.insideFraudProofWindow.will.return.with(
true
)
const proof = {
stateRoot: NULL_BYTES32,
stateRootBatchHeader: DUMMY_BATCH_HEADERS[0],
stateRootProof: DUMMY_BATCH_PROOFS[0],
stateTrieWitness: '0x',
storageTrieWitness: '0x',
}
await expect(
OVM_L1CrossDomainMessenger.relayMessage(
target,
sender,
message,
0,
proof
)
).to.be.revertedWith('Provided message could not be verified.')
})
it('should revert if provided an invalid state root proof', async () => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
false
)
const proof = {
stateRoot: NULL_BYTES32,
stateRootBatchHeader: DUMMY_BATCH_HEADERS[0],
stateRootProof: DUMMY_BATCH_PROOFS[0],
stateTrieWitness: '0x',
storageTrieWitness: '0x',
}
await expect(
OVM_L1CrossDomainMessenger.relayMessage(
target,
sender,
message,
0,
proof
)
).to.be.revertedWith('Provided message could not be verified.')
})
it('should revert if provided an invalid storage trie witness', async () => {
await expect(
OVM_L1CrossDomainMessenger.relayMessage(target, sender, message, 0, {
...proof,
storageTrieWitness: '0x',
})
).to.be.reverted
})
it('should revert if provided an invalid state trie witness', async () => {
await expect(
OVM_L1CrossDomainMessenger.relayMessage(target, sender, message, 0, {
...proof,
stateTrieWitness: '0x',
})
).to.be.reverted
})
it('should send a call to the target contract', async () => {
await OVM_L1CrossDomainMessenger.relayMessage(
target,
sender,
message,
0,
proof
)
expect(Mock__TargetContract.smocked.setTarget.calls[0]).to.deep.equal([
NON_ZERO_ADDRESS,
])
})
it('should revert if trying to send the same message twice', async () => {
await OVM_L1CrossDomainMessenger.relayMessage(
target,
sender,
message,
0,
proof
)
await expect(
OVM_L1CrossDomainMessenger.relayMessage(
target,
sender,
message,
0,
proof
)
).to.be.revertedWith('Provided message has already been received.')
})
})
})
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
/* Internal Imports */
import {
makeAddressManager,
setProxyTarget,
NON_NULL_BYTES32,
ZERO_ADDRESS,
NON_ZERO_ADDRESS,
} from '../../../helpers'
import { getContractInterface } from '../../../../src'
const getXDomainCalldata = (
sender: string,
target: string,
message: string,
messageNonce: number
): string => {
return getContractInterface(
'OVM_L2CrossDomainMessenger'
).encodeFunctionData('relayMessage', [target, sender, message, messageNonce])
}
describe('OVM_L2CrossDomainMessenger', () => {
let signer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
})
let AddressManager: Contract
before(async () => {
AddressManager = await makeAddressManager()
})
let Mock__TargetContract: MockContract
let Mock__OVM_L1CrossDomainMessenger: MockContract
let Mock__OVM_L1MessageSender: MockContract
let Mock__OVM_L2ToL1MessagePasser: MockContract
before(async () => {
Mock__TargetContract = smockit(
await ethers.getContractFactory('Helper_SimpleProxy')
)
Mock__OVM_L1CrossDomainMessenger = smockit(
await ethers.getContractFactory('OVM_L1CrossDomainMessenger')
)
Mock__OVM_L1MessageSender = smockit(
await ethers.getContractFactory('OVM_L1MessageSender')
)
Mock__OVM_L2ToL1MessagePasser = smockit(
await ethers.getContractFactory('OVM_L2ToL1MessagePasser')
)
await AddressManager.setAddress(
'OVM_L1CrossDomainMessenger',
Mock__OVM_L1CrossDomainMessenger.address
)
await setProxyTarget(
AddressManager,
'OVM_L1MessageSender',
Mock__OVM_L1MessageSender
)
await setProxyTarget(
AddressManager,
'OVM_L2ToL1MessagePasser',
Mock__OVM_L2ToL1MessagePasser
)
})
let Factory__OVM_L2CrossDomainMessenger: ContractFactory
before(async () => {
Factory__OVM_L2CrossDomainMessenger = await ethers.getContractFactory(
'OVM_L2CrossDomainMessenger'
)
})
let OVM_L2CrossDomainMessenger: Contract
beforeEach(async () => {
OVM_L2CrossDomainMessenger = await Factory__OVM_L2CrossDomainMessenger.deploy(
AddressManager.address
)
})
describe('sendMessage', () => {
const target = NON_ZERO_ADDRESS
const message = NON_NULL_BYTES32
const gasLimit = 100_000
it('should be able to send a single message', async () => {
await expect(
OVM_L2CrossDomainMessenger.sendMessage(target, message, gasLimit)
).to.not.be.reverted
expect(
Mock__OVM_L2ToL1MessagePasser.smocked.passMessageToL1.calls[0]
).to.deep.equal([
getXDomainCalldata(await signer.getAddress(), target, message, 0),
])
})
it('should be able to send the same message twice', async () => {
await OVM_L2CrossDomainMessenger.sendMessage(target, message, gasLimit)
await expect(
OVM_L2CrossDomainMessenger.sendMessage(target, message, gasLimit)
).to.not.be.reverted
})
})
describe('relayMessage', () => {
let target: string
let message: string
let sender: string
before(async () => {
target = Mock__TargetContract.address
message = Mock__TargetContract.interface.encodeFunctionData('setTarget', [
NON_ZERO_ADDRESS,
])
sender = await signer.getAddress()
})
beforeEach(async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
Mock__OVM_L1CrossDomainMessenger.address
)
})
it('should revert if the L1 message sender is not the OVM_L1CrossDomainMessenger', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
ZERO_ADDRESS
)
await expect(
OVM_L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
).to.be.revertedWith('Provided message could not be verified.')
})
it('should send a call to the target contract', async () => {
await OVM_L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
expect(Mock__TargetContract.smocked.setTarget.calls[0]).to.deep.equal([
NON_ZERO_ADDRESS,
])
})
it('should revert if trying to send the same message twice', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
Mock__OVM_L1CrossDomainMessenger.address
)
await OVM_L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
await expect(
OVM_L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
).to.be.revertedWith('Provided message has already been received.')
})
})
})
......@@ -2,46 +2,118 @@ import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract } from 'ethers'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
import _ from 'lodash'
/* Internal Imports */
import {
getProxyManager,
MockContract,
getMockContract,
makeAddressManager,
setProxyTarget,
NON_NULL_BYTES32,
FORCE_INCLUSION_PERIOD_SECONDS,
ZERO_ADDRESS,
setEthTime,
NON_ZERO_ADDRESS,
remove0x,
getEthTime,
getNextBlockNumber,
increaseEthTime,
// NON_NULL_BYTES32,
// ZERO_ADDRESS,
} from '../../../helpers'
import { defaultAbiCoder, keccak256 } from 'ethers/lib/utils'
const getEthTime = async (): Promise<number> => {
return (await ethers.provider.getBlock('latest')).timestamp
interface sequencerBatchContext {
numSequencedTransactions: Number
numSubsequentQueueTransactions: Number
timestamp: Number
blockNumber: Number
}
const setEthTime = async (time: number): Promise<void> => {
await ethers.provider.send('evm_setNextBlockTimestamp', [time])
const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16]
const getQueueElementHash = (queueIndex: number): string => {
return getChainElementHash(false, queueIndex, 0, 0, '0x')
}
const getSequencerElementHash = (
timestamp: number,
blockNumber: number,
txData: string
): string => {
return getChainElementHash(true, 0, timestamp, blockNumber, txData)
}
const getChainElementHash = (
isSequenced: boolean,
queueIndex: number,
timestamp: number,
blockNumber: number,
txData: string
): string => {
return keccak256(
defaultAbiCoder.encode(
['bool', 'uint256', 'uint256', 'uint256', 'bytes'],
[isSequenced, queueIndex, timestamp, blockNumber, txData]
)
)
}
const getTransactionHash = (
sender: string,
target: string,
gasLimit: number,
data: string
): string => {
return keccak256(encodeQueueTransaction(sender, target, gasLimit, data))
}
const encodeQueueTransaction = (
sender: string,
target: string,
gasLimit: number,
data: string
): string => {
return defaultAbiCoder.encode(
['address', 'address', 'uint256', 'bytes'],
[sender, target, gasLimit, data]
)
}
const encodeTimestampAndBlockNumber = (
timestamp: number,
blockNumber: number
): string => {
return (
'0x' +
remove0x(BigNumber.from(blockNumber).toHexString()).padStart(54, '0') +
remove0x(BigNumber.from(timestamp).toHexString()).padStart(10, '0')
)
}
describe('OVM_CanonicalTransactionChain', () => {
let signer: Signer
let sequencer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
;[signer, sequencer] = await ethers.getSigners()
})
let Proxy_Manager: Contract
let AddressManager: Contract
before(async () => {
Proxy_Manager = await getProxyManager()
AddressManager = await makeAddressManager()
await AddressManager.setAddress(
'OVM_Sequencer',
await sequencer.getAddress()
)
})
let Mock__OVM_L1ToL2TransactionQueue: MockContract
before(async () => {
Mock__OVM_L1ToL2TransactionQueue = await getMockContract(
Mock__OVM_L1ToL2TransactionQueue = smockit(
await ethers.getContractFactory('OVM_L1ToL2TransactionQueue')
)
await setProxyTarget(
Proxy_Manager,
AddressManager,
'OVM_L1ToL2TransactionQueue',
Mock__OVM_L1ToL2TransactionQueue
)
......@@ -57,317 +129,620 @@ describe('OVM_CanonicalTransactionChain', () => {
let OVM_CanonicalTransactionChain: Contract
beforeEach(async () => {
OVM_CanonicalTransactionChain = await Factory__OVM_CanonicalTransactionChain.deploy(
Proxy_Manager.address,
AddressManager.address,
FORCE_INCLUSION_PERIOD_SECONDS
)
})
describe('appendQueueBatch()', () => {
describe('when the L1ToL2TransactionQueue queue is empty', () => {
before(() => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [0])
})
describe('enqueue', () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
it('should revert when trying to input more data than the max data size', async () => {
const MAX_ROLLUP_TX_SIZE = await OVM_CanonicalTransactionChain.MAX_ROLLUP_TX_SIZE()
const data = '0x' + '12'.repeat(MAX_ROLLUP_TX_SIZE + 1)
it('should revert', async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch()
).to.be.revertedWith('No batches are currently queued to be appended.')
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.be.revertedWith('Transaction exceeds maximum rollup data size.')
})
it('should revert if gas limit parameter is not at least MIN_ROLLUP_TX_GAS', async () => {
const MIN_ROLLUP_TX_GAS = await OVM_CanonicalTransactionChain.MIN_ROLLUP_TX_GAS()
const gasLimit = MIN_ROLLUP_TX_GAS / 2
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.be.revertedWith('Layer 2 gas limit too low to enqueue.')
})
describe('when the L1ToL2TransactionQueue queue is not empty', () => {
before(() => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [1])
it('should revert if transaction gas limit does not cover rollup burn', async () => {
const L2_GAS_DISCOUNT_DIVISOR = await OVM_CanonicalTransactionChain.L2_GAS_DISCOUNT_DIVISOR()
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data, {
gasLimit: gasLimit / L2_GAS_DISCOUNT_DIVISOR - 1,
})
).to.be.revertedWith('Insufficient gas for L2 rate limiting burn.')
})
describe('when the inclusion delay period has not elapsed', () => {
beforeEach(async () => {
const timestamp = await getEthTime()
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('peek', [
{
describe('with valid input parameters', () => {
it('should emit a QueueTransactionAppended event', async () => {
const timestamp = (await getEthTime(ethers.provider)) + 100
const blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
const encodedTimestampAndBlockNumber = encodeTimestampAndBlockNumber(
timestamp,
batchRoot: NON_NULL_BYTES32,
isL1ToL2Batch: true,
},
])
await setEthTime(timestamp + FORCE_INCLUSION_PERIOD_SECONDS / 2)
})
blockNumber
)
const encodedQueueTransaction = encodeQueueTransaction(
await signer.getAddress(),
target,
gasLimit,
data
)
it('should revert', async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch()
).to.be.revertedWith(
'Cannot append until the inclusion delay period has elapsed.'
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
)
.to.emit(OVM_CanonicalTransactionChain, 'QueueTransactionAppended')
.withArgs(encodedQueueTransaction, encodedTimestampAndBlockNumber)
})
describe('when enqueing multiple times', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`should be able to enqueue ${size} elements`, async () => {
for (let i = 0; i < size; i++) {
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.not.be.reverted
}
})
}
})
})
})
describe('when the inclusion delay period has elapsed', () => {
beforeEach(async () => {
const timestamp = await getEthTime()
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('peek', [
{
timestamp,
batchRoot: NON_NULL_BYTES32,
isL1ToL2Batch: true,
},
])
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('dequeue', [
{
describe('getQueueElement', () => {
it('should revert when accessing a non-existent element', async () => {
await expect(
OVM_CanonicalTransactionChain.getQueueElement(0)
).to.be.revertedWith('Index too large')
})
describe('when the requested element exists', () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
describe('when getting the first element', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`gets the element when ${size} + 1 elements exist`, async () => {
const timestamp = (await getEthTime(ethers.provider)) + 100
const blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
const queueRoot = getTransactionHash(
await signer.getAddress(),
target,
gasLimit,
data
)
await OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
for (let i = 0; i < size; i++) {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
'0x' + '12'.repeat(i + 1)
)
}
expect(
_.toPlainObject(
await OVM_CanonicalTransactionChain.getQueueElement(0)
)
).to.deep.include({
queueRoot,
timestamp,
batchRoot: NON_NULL_BYTES32,
isL1ToL2Batch: true,
},
])
await setEthTime(timestamp + FORCE_INCLUSION_PERIOD_SECONDS)
blockNumber,
})
})
}
})
it('should append the top element of the queue and attempt to dequeue', async () => {
await expect(OVM_CanonicalTransactionChain.appendQueueBatch()).to.not
.be.reverted
describe('when getting the middle element', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`gets the element when ${size} elements exist`, async () => {
let timestamp: number
let blockNumber: number
let queueRoot: string
const middleIndex = Math.floor(size / 2)
for (let i = 0; i < size; i++) {
if (i === middleIndex) {
timestamp = (await getEthTime(ethers.provider)) + 100
blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
queueRoot = getTransactionHash(
await signer.getAddress(),
target,
gasLimit,
data
)
// TODO: Check that the batch root was inserted.
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
} else {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
'0x' + '12'.repeat(i + 1)
)
}
}
expect(
Mock__OVM_L1ToL2TransactionQueue.getCallCount('dequeue')
).to.equal(1)
})
_.toPlainObject(
await OVM_CanonicalTransactionChain.getQueueElement(middleIndex)
)
).to.deep.include({
queueRoot,
timestamp,
blockNumber,
})
})
}
})
describe('appendSequencerBatch()', () => {
describe('when the sender is not the sequencer', () => {
before(async () => {
await Proxy_Manager.setProxy('Sequencer', ZERO_ADDRESS)
})
describe('when getting the last element', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`gets the element when ${size} elements exist`, async () => {
let timestamp: number
let blockNumber: number
let queueRoot: string
for (let i = 0; i < size; i++) {
if (i === size - 1) {
timestamp = (await getEthTime(ethers.provider)) + 100
blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
queueRoot = getTransactionHash(
await signer.getAddress(),
target,
gasLimit,
data
)
it('should revert', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch([], 0)
).to.be.revertedWith('Function can only be called by the Sequencer.')
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
} else {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
'0x' + '12'.repeat(i + 1)
)
}
}
expect(
_.toPlainObject(
await OVM_CanonicalTransactionChain.getQueueElement(size - 1)
)
).to.deep.include({
queueRoot,
timestamp,
blockNumber,
})
})
}
})
})
describe('when the sender is the sequencer', () => {
before(async () => {
await Proxy_Manager.setProxy('Sequencer', await signer.getAddress())
})
describe('when the given batch is empty', () => {
const batch = []
it('should revert', async () => {
describe('appendQueueBatch', () => {
it('should revert if trying to append zero transactions', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(batch, 0)
).to.be.revertedWith('Cannot submit an empty batch.')
OVM_CanonicalTransactionChain.appendQueueBatch(0)
).to.be.revertedWith('Must append more than zero transactions.')
})
it('should revert if the queue is empty', async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch(1)
).to.be.revertedWith('Index too large.')
})
describe('when the given batch is not empty', () => {
const batch = [NON_NULL_BYTES32]
describe('when the queue is not empty', () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
describe('when the timestamp is not greater than the previous OVM timestamp', () => {
const timestamp = 0
for (const size of ELEMENT_TEST_SIZES) {
describe(`when the queue has ${size} elements`, () => {
beforeEach(async () => {
for (let i = 0; i < size; i++) {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
}
})
it('should revert', async () => {
describe('when the sequencer inclusion period has not passed', () => {
it('should revert if not called by the sequencer', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
batch,
timestamp
OVM_CanonicalTransactionChain.connect(signer).appendQueueBatch(
1
)
).to.be.revertedWith(
'Batch timestamp must be later than the last OVM timestamp.'
'Queue transactions cannot be submitted during the sequencer inclusion period.'
)
})
it('should succeed if called by the sequencer', async () => {
await expect(
OVM_CanonicalTransactionChain.connect(
sequencer
).appendQueueBatch(1)
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, 1)
})
})
describe('when the timestamp is greater than the previous OVM timestamp', () => {
const timestamp = 1000
describe('when the sequencer inclusion period has passed', () => {
beforeEach(async () => {
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS * 2
)
})
describe('when the queue is not empty', () => {
before(() => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [1])
it('should be able to append a single element', async () => {
await expect(OVM_CanonicalTransactionChain.appendQueueBatch(1))
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, 1)
})
describe('when the first element in the queue is older than the provided batch', () => {
before(() => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('peek', [
{
timestamp: timestamp / 2,
batchRoot: NON_NULL_BYTES32,
isL1ToL2Batch: true,
},
])
it(`should be able to append ${size} elements`, async () => {
await expect(OVM_CanonicalTransactionChain.appendQueueBatch(size))
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, size)
})
it(`should revert if appending ${size} + 1 elements`, async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch(size + 1)
).to.be.revertedWith('Index too large.')
})
})
})
}
})
})
it('should revert', async () => {
describe('appendSequencerBatch', () => {
beforeEach(() => {
OVM_CanonicalTransactionChain = OVM_CanonicalTransactionChain.connect(
sequencer
)
})
it('should revert if expected start does not match current total batches', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
batch,
timestamp
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
1234,
1
)
).to.be.revertedWith(
'Older queue batches must be processed before a newer sequencer batch.'
'Actual batch start index does not match expected start index.'
)
})
})
describe('when the first element in the queue is not older than the provided batch', () => {
before(() => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('peek', [
it('should revert if not called by the sequencer', async () => {
await expect(
OVM_CanonicalTransactionChain.connect(signer).appendSequencerBatch(
['0x1234'],
[
{
timestamp,
batchRoot: NON_NULL_BYTES32,
isL1ToL2Batch: true,
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
])
],
0,
1
)
).to.be.revertedWith('Function can only be called by the Sequencer.')
})
it('should insert the sequencer batch', async () => {
it('should revert if no contexts are provided', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(['0x1234'], [], 0, 1)
).to.be.revertedWith('Must provide at least one batch context.')
})
it('should revert if total elements to append is zero', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
batch,
timestamp
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
0,
0
)
).to.not.be.reverted
// TODO: Check that the batch was inserted correctly.
})
})
).to.be.revertedWith('Must append at least one element.')
})
describe('when the queue is empty', async () => {
before(() => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [0])
for (const size of ELEMENT_TEST_SIZES) {
describe(`when appending ${size} sequencer transactions`, () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
beforeEach(async () => {
await OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
})
it('should insert the sequencer batch', async () => {
it('should revert if a queue element needs to be processed', async () => {
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS * 2
)
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
batch,
timestamp
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
0,
1
)
).to.not.be.reverted
// TODO: Check that the batch was inserted correctly.
})
})
})
})
})
})
describe('getTotalElements()', () => {
describe('when no batch elements have been inserted', () => {
it('should return zero', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(
0
).to.be.revertedWith(
'Older queue batches must be processed before a new sequencer batch.'
)
})
})
describe('when one batch element has been inserted', () => {
beforeEach(async () => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [0])
await OVM_CanonicalTransactionChain.appendSequencerBatch(
[NON_NULL_BYTES32],
1000
it('should revert if the context timestamp is <= the head queue element timestamp', async () => {
const timestamp = (await getEthTime(ethers.provider)) + 1000
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: 0,
},
],
0,
1
)
).to.be.revertedWith('Sequencer transactions timestamp too high.')
})
it('should return the number of inserted batch elements', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(
it('should revert if the context block number is <= the head queue element block number', async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber = (await getNextBlockNumber(ethers.provider)) + 100
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: blockNumber,
},
],
0,
1
)
})
).to.be.revertedWith('Sequencer transactions blockNumber too high.')
})
describe('when 64 batch elements have been inserted in one batch', () => {
const batch = Array(64).fill(NON_NULL_BYTES32)
describe('when not inserting queue elements in between', () => {
describe('when using a single batch context', () => {
let contexts: any[]
let transactions: any[]
beforeEach(async () => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [0])
await OVM_CanonicalTransactionChain.appendSequencerBatch(batch, 1000)
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) - 10
contexts = [
{
numSequencedTransactions: size,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: blockNumber,
},
]
transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
})
it('should return the number of inserted batch elements', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(
64
it('should append the given number of transactions', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
transactions,
contexts,
0,
size
)
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, 0)
})
})
})
describe('when 32 batch elements have been inserted in each of two batches', () => {
const batch = Array(32).fill(NON_NULL_BYTES32)
describe('when inserting queue elements in between', () => {
beforeEach(async () => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [0])
await OVM_CanonicalTransactionChain.appendSequencerBatch(batch, 1000)
await OVM_CanonicalTransactionChain.appendSequencerBatch(batch, 2000)
for (let i = 0; i < size; i++) {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
}
})
it('should return the number of inserted batch elements', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(
64
)
describe('between every other sequencer transaction', () => {
let contexts: any[]
let transactions: any[]
beforeEach(async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) - 50
contexts = [...Array(size)].map(() => {
return {
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 1,
timestamp: timestamp,
blockNumber: Math.max(blockNumber, 0),
}
})
transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
})
describe('getTotalBatches()', () => {
describe('when no batches have been inserted', () => {
it('should return zero', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalBatches()).to.equal(
0
it('should append the batch', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
transactions,
contexts,
0,
size * 2
)
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, size)
})
})
describe('when one batch has been inserted', () => {
describe(`between every ${Math.max(
Math.floor(size / 8),
1
)} sequencer transaction`, () => {
const spacing = Math.max(Math.floor(size / 8), 1)
let contexts: any[]
let transactions: any[]
beforeEach(async () => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [0])
await OVM_CanonicalTransactionChain.appendSequencerBatch(
[NON_NULL_BYTES32],
1000
)
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) - 50
contexts = [...Array(spacing)].map(() => {
return {
numSequencedTransactions: size / spacing,
numSubsequentQueueTransactions: 1,
timestamp: timestamp,
blockNumber: Math.max(blockNumber, 0),
}
})
it('should return the number of inserted batch elements', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalBatches()).to.equal(
1
)
transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
})
describe('when 8 batches have been inserted', () => {
beforeEach(async () => {
Mock__OVM_L1ToL2TransactionQueue.setReturnValues('size', [0])
for (let i = 0; i < 8; i++) {
await OVM_CanonicalTransactionChain.appendSequencerBatch(
[NON_NULL_BYTES32],
1000 * (i + 1)
it('should append the batch', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
transactions,
contexts,
0,
size + spacing
)
}
})
it('should return the number of inserted batch elements', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalBatches()).to.equal(
8
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, spacing)
})
})
})
})
}
})
describe('verifyElement()', () => {
it('should revert when given an invalid batch header', async () => {
// TODO
describe('getTotalElements', () => {
it('should return zero when no elements exist', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(0)
})
it('should revert when given an invalid inclusion proof', async () => {
// TODO
for (const size of ELEMENT_TEST_SIZES) {
describe(`when the sequencer inserts a batch of ${size} elements`, () => {
beforeEach(async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 10
const contexts = [
{
numSequencedTransactions: size,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: Math.max(blockNumber, 0),
},
]
const transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
await OVM_CanonicalTransactionChain.connect(
sequencer
).appendSequencerBatch(transactions, contexts, 0, size)
})
it('should return true when given a valid proof', async () => {
// TODO
it(`should return ${size}`, async () => {
expect(
await OVM_CanonicalTransactionChain.getTotalElements()
).to.equal(size)
})
})
}
})
})
......@@ -3,16 +3,19 @@ import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
/* Internal Imports */
import {
getProxyManager,
MockContract,
getMockContract,
makeAddressManager,
setProxyTarget,
NON_NULL_BYTES32,
ZERO_ADDRESS,
toHexString32,
getEthTime,
NULL_BYTES32,
} from '../../../helpers'
import { keccak256, defaultAbiCoder } from 'ethers/lib/utils'
describe('OVM_StateCommitmentChain', () => {
let signer: Signer
......@@ -20,19 +23,19 @@ describe('OVM_StateCommitmentChain', () => {
;[signer] = await ethers.getSigners()
})
let Proxy_Manager: Contract
let AddressManager: Contract
before(async () => {
Proxy_Manager = await getProxyManager()
AddressManager = await makeAddressManager()
})
let Mock__OVM_CanonicalTransactionChain: MockContract
before(async () => {
Mock__OVM_CanonicalTransactionChain = await getMockContract(
Mock__OVM_CanonicalTransactionChain = smockit(
await ethers.getContractFactory('OVM_CanonicalTransactionChain')
)
await setProxyTarget(
Proxy_Manager,
AddressManager,
'OVM_CanonicalTransactionChain',
Mock__OVM_CanonicalTransactionChain
)
......@@ -48,7 +51,7 @@ describe('OVM_StateCommitmentChain', () => {
let OVM_StateCommitmentChain: Contract
beforeEach(async () => {
OVM_StateCommitmentChain = await Factory__OVM_StateCommitmentChain.deploy(
Proxy_Manager.address
AddressManager.address
)
})
......@@ -68,9 +71,8 @@ describe('OVM_StateCommitmentChain', () => {
describe('when submitting more elements than present in the OVM_CanonicalTransactionChain', () => {
before(() => {
Mock__OVM_CanonicalTransactionChain.setReturnValues(
'getTotalElements',
[batch.length - 1]
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length - 1
)
})
......@@ -85,43 +87,43 @@ describe('OVM_StateCommitmentChain', () => {
describe('when not submitting more elements than present in the OVM_CanonicalTransactionChain', () => {
before(() => {
Mock__OVM_CanonicalTransactionChain.setReturnValues(
'getTotalElements',
[batch.length]
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length
)
})
it('should append the state batch', async () => {
await expect(OVM_StateCommitmentChain.appendStateBatch(batch)).to.not
.be.reverted
// TODO: Check for correct insertion.
})
})
})
})
describe('deleteStateBatch', () => {
// TODO: Calculate the right header.
const batch = [NON_NULL_BYTES32]
const batchHeader = {
batchIndex: 0,
batchRoot: NON_NULL_BYTES32,
batchSize: 0,
batchRoot: keccak256(NON_NULL_BYTES32),
batchSize: 1,
prevTotalElements: 0,
extraData: '0x',
extraData: NULL_BYTES32,
}
beforeEach(async () => {
Mock__OVM_CanonicalTransactionChain.setReturnValues('getTotalElements', [
batch.length,
])
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length
)
await OVM_StateCommitmentChain.appendStateBatch(batch)
batchHeader.extraData = defaultAbiCoder.encode(
['uint256'],
[await getEthTime(ethers.provider)]
)
})
describe('when the sender is not the OVM_FraudVerifier', () => {
before(async () => {
await Proxy_Manager.setProxy('OVM_FraudVerifier', ZERO_ADDRESS)
await AddressManager.setAddress('OVM_FraudVerifier', ZERO_ADDRESS)
})
it('should revert', async () => {
......@@ -135,7 +137,7 @@ describe('OVM_StateCommitmentChain', () => {
describe('when the sender is the OVM_FraudVerifier', () => {
before(async () => {
await Proxy_Manager.setProxy(
await AddressManager.setAddress(
'OVM_FraudVerifier',
await signer.getAddress()
)
......@@ -158,7 +160,7 @@ describe('OVM_StateCommitmentChain', () => {
await expect(
OVM_StateCommitmentChain.deleteStateBatch({
...batchHeader,
extraData: '0x1234',
extraData: '0x' + '22'.repeat(32),
})
).to.be.revertedWith('Invalid batch header.')
})
......@@ -168,8 +170,6 @@ describe('OVM_StateCommitmentChain', () => {
it('should remove the batch and all following batches', async () => {
await expect(OVM_StateCommitmentChain.deleteStateBatch(batchHeader))
.to.not.be.reverted
// TODO: Check that it deleted the batches.
})
})
})
......@@ -186,9 +186,8 @@ describe('OVM_StateCommitmentChain', () => {
describe('when one batch element has been inserted', () => {
beforeEach(async () => {
const batch = [NON_NULL_BYTES32]
Mock__OVM_CanonicalTransactionChain.setReturnValues(
'getTotalElements',
[batch.length]
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length
)
await OVM_StateCommitmentChain.appendStateBatch(batch)
})
......@@ -201,9 +200,8 @@ describe('OVM_StateCommitmentChain', () => {
describe('when 64 batch elements have been inserted in one batch', () => {
beforeEach(async () => {
const batch = Array(64).fill(NON_NULL_BYTES32)
Mock__OVM_CanonicalTransactionChain.setReturnValues(
'getTotalElements',
[batch.length]
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length
)
await OVM_StateCommitmentChain.appendStateBatch(batch)
})
......@@ -216,9 +214,8 @@ describe('OVM_StateCommitmentChain', () => {
describe('when 32 batch elements have been inserted in each of two batches', () => {
beforeEach(async () => {
const batch = Array(32).fill(NON_NULL_BYTES32)
Mock__OVM_CanonicalTransactionChain.setReturnValues(
'getTotalElements',
[batch.length * 2]
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length * 2
)
await OVM_StateCommitmentChain.appendStateBatch(batch)
await OVM_StateCommitmentChain.appendStateBatch(batch)
......@@ -240,9 +237,8 @@ describe('OVM_StateCommitmentChain', () => {
describe('when one batch has been inserted', () => {
beforeEach(async () => {
const batch = [NON_NULL_BYTES32]
Mock__OVM_CanonicalTransactionChain.setReturnValues(
'getTotalElements',
[batch.length]
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length
)
await OVM_StateCommitmentChain.appendStateBatch(batch)
})
......@@ -255,10 +251,10 @@ describe('OVM_StateCommitmentChain', () => {
describe('when 8 batches have been inserted', () => {
beforeEach(async () => {
const batch = [NON_NULL_BYTES32]
Mock__OVM_CanonicalTransactionChain.setReturnValues(
'getTotalElements',
[batch.length * 8]
Mock__OVM_CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length * 8
)
for (let i = 0; i < 8; i++) {
await OVM_StateCommitmentChain.appendStateBatch(batch)
}
......
/* Internal Imports */
import {
ExecutionManagerTestRunner,
TestDefinition,
NON_NULL_BYTES32,
OVM_TX_GAS_LIMIT,
} from '../../../../helpers'
const globalContext = {
ovmCHAINID: 420,
}
const transactionContext = {
ovmTIMESTAMP: 12341234,
ovmNUMBER: 13371337,
ovmGASLIMIT: 45674567,
ovmTXGASLIMIT: 78907890,
ovmL1QUEUEORIGIN: 1,
ovmL1TXORIGIN: '0x1234123412341234123412341234123412341234',
}
const messageContext = {
ovmCALLER: '0x6789678967896789678967896789678967896789',
ovmADDRESS: '0x4567456745674567456745674567456745674567',
}
const test_contextOpcodes: TestDefinition = {
name: 'unit tests for basic getter opcodes',
preState: {
ExecutionManager: {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
globalContext,
transactionContext,
messageContext,
},
StateManager: {
owner: '$OVM_EXECUTION_MANAGER',
accounts: {
$DUMMY_OVM_ADDRESS_1: {
codeHash: NON_NULL_BYTES32,
ethAddress: '$OVM_CALL_HELPER',
},
},
},
},
parameters: [
// TODO: re-enable when we can unwrap tests' ovmCALL
// {
// name: 'gets ovmCALLER',
// steps: [
// {
// functionName: 'ovmCALLER',
// expectedReturnValue: messageContext.ovmCALLER
// }
// ],
// },
// {
// name: 'gets ovmADDRESS',
// steps: [
// {
// functionName: 'ovmADDRESS',
// expectedReturnValue: messageContext.ovmADDRESS
// }
// ],
// },
{
name: 'gets ovmTIMESTAMP',
steps: [
{
functionName: 'ovmTIMESTAMP',
expectedReturnValue: transactionContext.ovmTIMESTAMP,
},
],
},
{
name: 'gets ovmNUMBER',
steps: [
{
functionName: 'ovmNUMBER',
expectedReturnValue: transactionContext.ovmNUMBER,
},
],
},
{
name: 'gets ovmGASLIMIT',
steps: [
{
functionName: 'ovmGASLIMIT',
expectedReturnValue: transactionContext.ovmGASLIMIT,
},
],
},
{
name: 'gets ovmL1QUEUEORIGIN',
steps: [
{
functionName: 'ovmL1QUEUEORIGIN',
expectedReturnValue: transactionContext.ovmL1QUEUEORIGIN,
},
],
},
{
name: 'gets ovmL1TXORIGIN',
steps: [
{
functionName: 'ovmL1TXORIGIN',
expectedReturnValue: transactionContext.ovmL1TXORIGIN,
},
],
},
{
name: 'gets ovmCHAINID',
steps: [
{
functionName: 'ovmCHAINID',
expectedReturnValue: globalContext.ovmCHAINID,
},
],
},
],
}
const runner = new ExecutionManagerTestRunner()
runner.run(test_contextOpcodes)
/* Internal Imports */
import {
ExecutionManagerTestRunner,
TestDefinition,
OVM_TX_GAS_LIMIT,
NON_NULL_BYTES32,
REVERT_FLAGS,
VERIFIED_EMPTY_CONTRACT_HASH,
NUISANCE_GAS_COSTS,
Helper_TestRunner_BYTELEN,
ZERO_ADDRESS,
} from '../../../../helpers'
const DUMMY_REVERT_DATA =
'0xdeadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420'
const CREATED_CONTRACT_1 = '0x2bda4a99d5be88609d23b1e4ab5d1d34fb1c2feb'
const FRESH_CALL_NUISANCE_GAS_COST =
Helper_TestRunner_BYTELEN *
NUISANCE_GAS_COSTS.NUISANCE_GAS_PER_CONTRACT_BYTE +
NUISANCE_GAS_COSTS.MIN_NUISANCE_GAS_PER_CONTRACT
const test_nuisanceGas: TestDefinition = {
name: 'Basic tests for nuisance gas',
preState: {
ExecutionManager: {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
owner: '$OVM_EXECUTION_MANAGER',
accounts: {
$DUMMY_OVM_ADDRESS_1: {
codeHash: NON_NULL_BYTES32,
ethAddress: '$OVM_CALL_HELPER',
},
$DUMMY_OVM_ADDRESS_2: {
codeHash: NON_NULL_BYTES32,
ethAddress: '$OVM_CALL_HELPER',
},
$DUMMY_OVM_ADDRESS_3: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
[CREATED_CONTRACT_1]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
},
},
},
subTests: [
{
name:
'ovmCALL consumes nuisance gas of CODESIZE * NUISANCE_GAS_PER_CONTRACT_BYTE',
postState: {
ExecutionManager: {
messageRecord: {
nuisanceGasLeft: OVM_TX_GAS_LIMIT - FRESH_CALL_NUISANCE_GAS_COST,
},
},
},
parameters: [
{
name: 'single ovmCALL',
steps: [
// do a non-nuisance-gas-consuming opcode (test runner auto-wraps in ovmCALL)
{
functionName: 'ovmADDRESS',
expectedReturnValue: '$DUMMY_OVM_ADDRESS_1',
},
],
},
{
name: 'nested ovmCALL, same address',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [],
},
expectedReturnStatus: true,
},
],
},
],
},
{
name:
'ovmCALL consumes nuisance gas of CODESIZE * NUISANCE_GAS_PER_CONTRACT_BYTE twice for two unique ovmCALLS',
postState: {
ExecutionManager: {
messageRecord: {
nuisanceGasLeft:
OVM_TX_GAS_LIMIT - 2 * FRESH_CALL_NUISANCE_GAS_COST,
},
},
},
parameters: [
{
name: 'directly nested ovmCALL',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [],
},
expectedReturnStatus: true,
},
],
},
{
name: 'with a call to previously called contract too',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [],
},
expectedReturnStatus: true,
},
],
},
expectedReturnStatus: true,
},
],
},
],
},
{
name:
'ovmCALL consumes all allotted nuisance gas if code contract throws unknown exception',
postState: {
ExecutionManager: {
messageRecord: {
nuisanceGasLeft:
OVM_TX_GAS_LIMIT -
FRESH_CALL_NUISANCE_GAS_COST -
OVM_TX_GAS_LIMIT / 2,
},
},
},
parameters: [
{
name: 'give 1/2 gas to evmINVALID',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'evmINVALID',
},
],
},
expectedReturnStatus: true,
expectedReturnValue: {
ovmSuccess: false,
returnData: '0x',
},
},
],
},
],
},
{
name:
'ovmCREATE consumes all allotted nuisance gas if creation code throws data-less exception',
postState: {
ExecutionManager: {
messageRecord: {
// note: this is slightly higher than the "idealized" value which would be:
// OVM_TX_GAS_LIMIT / 2 - 2 * ( Helper_TestRunner_BYTELEN * NUISANCE_GAS_PER_CONTRACT_BYTE )
// This is because there is natural gas consumption between the ovmCALL(GAS/2) and ovmCREATE, which allots nuisance gas via _getNuisanceGasLimit.
// This means that the ovmCREATE exception, DOES consumes all nuisance gas allotted, but that allotment
// is less than the full OVM_TX_GAS_LIMIT / 2 which is alloted to the parent ovmCALL.
nuisanceGasLeft: 4531286,
},
},
},
parameters: [
{
name: 'give 1/2 gas to ovmCALL => ovmCREATE, evmINVALID',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
target: '$DUMMY_OVM_ADDRESS_1',
gasLimit: OVM_TX_GAS_LIMIT / 2,
subSteps: [
{
functionName: 'ovmCREATE',
functionParams: {
subSteps: [
{
functionName: 'evmINVALID',
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
},
],
},
expectedReturnStatus: true,
},
],
},
],
},
],
}
const runner = new ExecutionManagerTestRunner()
runner.run(test_nuisanceGas)
......@@ -2,7 +2,7 @@
import {
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
OVM_TX_GAS_LIMIT,
NON_NULL_BYTES32,
REVERT_FLAGS,
VERIFIED_EMPTY_CONTRACT_HASH,
......@@ -18,7 +18,7 @@ const test_ovmCALL: TestDefinition = {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
......@@ -37,6 +37,11 @@ const test_ovmCALL: TestDefinition = {
ethAddress: '0x' + '00'.repeat(20),
},
},
verifiedContractStorage: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: true,
},
},
},
},
parameters: [
......@@ -46,7 +51,7 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -65,7 +70,7 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -89,7 +94,7 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -115,7 +120,7 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -139,13 +144,13 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
......@@ -172,13 +177,13 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_3',
calldata: '0x',
},
......@@ -197,7 +202,7 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -220,7 +225,7 @@ const test_ovmCALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......
......@@ -2,7 +2,7 @@
import {
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
OVM_TX_GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
......@@ -11,11 +11,14 @@ import {
VERIFIED_EMPTY_CONTRACT_HASH,
DUMMY_BYTECODE_BYTELEN,
DUMMY_BYTECODE_HASH,
getStorageXOR,
} from '../../../../helpers'
const CREATED_CONTRACT_1 = '0xa1c4ba6fe56bda6db9df39bf45dbfc3cd104bd6f'
const CREATED_CONTRACT_1 = '0x2bda4a99d5be88609d23b1e4ab5d1d34fb1c2feb'
const CREATED_CONTRACT_2 = '0x2bda4a99d5be88609d23b1e4ab5d1d34fb1c2feb'
const NESTED_CREATED_CONTRACT = '0xb99a3d1d1e3f0bd867570da4776221c1b0b74ea3'
const CREATED_CONTRACT_BY_2_1 = '0xe0d8be8101f36ebe6b01abacec884422c39a1f62'
const CREATED_CONTRACT_BY_2_2 = '0x15ac629e1a3866b17179ee4ae86de5cbda744335'
const NESTED_CREATED_CONTRACT = '0xcb964b3f4162a0d4f5c997b40e19da5a546bc36f'
const DUMMY_REVERT_DATA =
'0xdeadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420'
......@@ -26,7 +29,7 @@ const test_ovmCREATE: TestDefinition = {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
......@@ -48,12 +51,28 @@ const test_ovmCREATE: TestDefinition = {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
[CREATED_CONTRACT_BY_2_1]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
[CREATED_CONTRACT_BY_2_2]: {
codeHash: '0x' + '01'.repeat(32),
ethAddress: '0x' + '00'.repeat(20),
},
[NESTED_CREATED_CONTRACT]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
},
contractStorage: {
$DUMMY_OVM_ADDRESS_2: {
[NULL_BYTES32]: getStorageXOR(NULL_BYTES32),
},
},
verifiedContractStorage: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: true,
},
$DUMMY_OVM_ADDRESS_2: {
[NULL_BYTES32]: true,
},
......@@ -278,7 +297,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -358,7 +377,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: CREATED_CONTRACT_1,
subSteps: [
{
......@@ -394,7 +413,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -417,7 +436,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......@@ -445,7 +464,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
......@@ -474,7 +493,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
......@@ -502,7 +521,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_3',
calldata: '0x',
},
......@@ -518,6 +537,38 @@ const test_ovmCREATE: TestDefinition = {
},
],
},
{
name: 'ovmCREATE => ovmCREATE => ovmCALL(ADDRESS_NONEXIST)',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmCREATE',
functionParams: {
subSteps: [
{
functionName: 'ovmCREATE',
functionParams: {
bytecode: '0x',
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
},
],
},
expectedReturnStatus: true,
expectedReturnValue: CREATED_CONTRACT_BY_2_1,
},
],
},
expectedReturnStatus: true,
},
],
},
{
name: 'ovmCREATE => ovmCREATE => ovmCALL(ADDRESS_NONEXIST)',
expectInvalidStateAccess: true,
......@@ -533,7 +584,7 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_3',
calldata: '0x',
},
......@@ -554,6 +605,39 @@ const test_ovmCREATE: TestDefinition = {
},
],
},
{
name: 'OZ-AUDIT: ovmCREATE => ((ovmCREATE => ovmADDRESS), ovmREVERT)',
steps: [
{
functionName: 'ovmCREATE',
functionParams: {
subSteps: [
{
functionName: 'ovmCREATE',
functionParams: {
subSteps: [
{
functionName: 'ovmADDRESS',
expectedReturnValue: NESTED_CREATED_CONTRACT,
},
],
},
expectedReturnStatus: true,
expectedReturnValue: NESTED_CREATED_CONTRACT,
},
{
functionName: 'ovmREVERT',
revertData: DUMMY_REVERT_DATA,
expectedReturnStatus: true,
expectedReturnValue: '0x00',
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
},
],
},
{
name: 'ovmCREATE => OUT_OF_GAS',
steps: [
......
......@@ -2,7 +2,7 @@
import {
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
OVM_TX_GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
......@@ -20,7 +20,7 @@ const test_ovmDELEGATECALL: TestDefinition = {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
......@@ -51,6 +51,11 @@ const test_ovmDELEGATECALL: TestDefinition = {
ethAddress: '0x' + '00'.repeat(20),
},
},
verifiedContractStorage: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: true,
},
},
},
},
parameters: [
......@@ -60,13 +65,13 @@ const test_ovmDELEGATECALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
......@@ -90,19 +95,19 @@ const test_ovmDELEGATECALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
......@@ -130,13 +135,13 @@ const test_ovmDELEGATECALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
......@@ -171,13 +176,13 @@ const test_ovmDELEGATECALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
......@@ -205,25 +210,25 @@ const test_ovmDELEGATECALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_3',
subSteps: [
{
......@@ -255,25 +260,25 @@ const test_ovmDELEGATECALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_3',
subSteps: [
{
......@@ -305,25 +310,25 @@ const test_ovmDELEGATECALL: TestDefinition = {
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmDELEGATECALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_3',
subSteps: [
{
......
/* Internal Imports */
import {
runExecutionManagerTest,
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
NULL_BYTES32,
OVM_TX_GAS_LIMIT,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE,
} from '../../../../helpers'
const test_ovmREVERT: TestDefinition = {
name: 'basic ovmREVERT unit tests',
name: 'Basic tests for ovmREVERT',
preState: {
ExecutionManager: {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
......@@ -31,34 +29,30 @@ const test_ovmREVERT: TestDefinition = {
},
parameters: [
{
name: 'ovmREVERT inside ovmCALL should cause EM to revert',
parameters: [
{
name: 'ovmCALL => ovmREVERT',
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
'$DUMMY_OVM_ADDRESS_1',
[
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmREVERT',
functionParams: ['0xdeadbeef'],
revertData: '0xdeadbeef',
expectedReturnStatus: false,
expectedReturnValues: [
REVERT_FLAGS.INTENTIONAL_REVERT,
'0xdeadbeef',
GAS_LIMIT / 2,
0,
],
expectedReturnValue: {
flag: REVERT_FLAGS.INTENTIONAL_REVERT,
data: '0xdeadbeef',
nuisanceGasLeft: OVM_TX_GAS_LIMIT / 2,
ovmGasRefund: 0,
},
],
],
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
expectedReturnStatus: false,
expectedReturnValue: '0xdeadbeef',
},
],
},
// TODO: fix this. only way to do it is manually set up and call ovmREVERT directly inside a context which mirrors that during creation.
......@@ -103,4 +97,5 @@ const test_ovmREVERT: TestDefinition = {
],
}
runExecutionManagerTest(test_ovmREVERT)
const runner = new ExecutionManagerTestRunner()
runner.run(test_ovmREVERT)
/* Internal Imports */
import {
runExecutionManagerTest,
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
OVM_TX_GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE,
getStorageXOR,
} from '../../../../helpers'
const test_ovmSLOAD: TestDefinition = {
name:
'External storage manipulation during initcode subcalls should correctly NOT be persisted if ovmREVERTed',
name: 'Basic tests for ovmSLOAD',
preState: {
ExecutionManager: {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
......@@ -32,6 +30,11 @@ const test_ovmSLOAD: TestDefinition = {
ethAddress: '$OVM_CALL_HELPER',
},
},
contractStorage: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: getStorageXOR(NULL_BYTES32),
},
},
verifiedContractStorage: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: true,
......@@ -41,27 +44,30 @@ const test_ovmSLOAD: TestDefinition = {
},
parameters: [
{
name: 'ovmCALL => ovmSLOAD',
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_1',
[
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
functionParams: {
key: NON_NULL_BYTES32,
},
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32],
expectedReturnValue: NULL_BYTES32,
},
],
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
],
}
runExecutionManagerTest(test_ovmSLOAD)
const runner = new ExecutionManagerTestRunner()
runner.run(test_ovmSLOAD)
/* Internal Imports */
import {
runExecutionManagerTest,
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
OVM_TX_GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE,
getStorageXOR,
} from '../../../../helpers'
const test_ovmSTATICCALL: TestDefinition = {
name: 'Basic checks on staticcall',
name: 'Basic tests for ovmSTATICCALL',
preState: {
ExecutionManager: {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
......@@ -30,9 +31,24 @@ const test_ovmSTATICCALL: TestDefinition = {
codeHash: NON_NULL_BYTES32,
ethAddress: '$OVM_CALL_HELPER',
},
$DUMMY_OVM_ADDRESS_3: {
codeHash: NON_NULL_BYTES32,
ethAddress: '$OVM_CALL_HELPER',
},
},
contractStorage: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: getStorageXOR(NULL_BYTES32),
},
$DUMMY_OVM_ADDRESS_3: {
[NON_NULL_BYTES32]: getStorageXOR(NULL_BYTES32),
},
},
verifiedContractStorage: {
$DUMMY_OVM_ADDRESS_2: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: true,
},
$DUMMY_OVM_ADDRESS_3: {
[NON_NULL_BYTES32]: true,
},
},
......@@ -40,241 +56,288 @@ const test_ovmSTATICCALL: TestDefinition = {
},
parameters: [
{
name: 'ovmSTATICCALL => ovmSSTORE',
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_1',
[
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT / 2,
'$DUMMY_OVM_ADDRESS_2',
[
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NULL_BYTES32],
functionParams: {
key: NULL_BYTES32,
value: NULL_BYTES32,
},
expectedReturnStatus: false,
expectedReturnValues: [
REVERT_FLAGS.STATIC_VIOLATION,
'0x',
GAS_LIMIT / 2,
0,
expectedReturnValue: {
flag: REVERT_FLAGS.STATIC_VIOLATION,
nuisanceGasLeft: OVM_TX_GAS_LIMIT / 2,
},
},
],
},
{
functionName: 'ovmCREATE',
functionParams: [DUMMY_BYTECODE, false, []],
expectedReturnStatus: false,
expectedReturnValues: [
REVERT_FLAGS.STATIC_VIOLATION,
'0x',
GAS_LIMIT / 2,
0,
},
],
},
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32],
},
name: 'ovmSTATICCALL => ovmSLOAD',
steps: [
{
functionName: 'ovmCALLER',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ['$DUMMY_OVM_ADDRESS_1'],
},
functionName: 'ovmSTATICCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmADDRESS',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ['$DUMMY_OVM_ADDRESS_2'],
functionName: 'ovmSLOAD',
functionParams: {
key: NON_NULL_BYTES32,
},
],
],
expectedReturnStatus: true,
expectedReturnValues: [],
expectedReturnValue: NULL_BYTES32,
},
],
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
{
name: 'ovmSTATICCALL => ovmCREATE',
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_1',
[
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_2',
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_2',
[
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32],
functionName: 'ovmCREATE',
functionParams: {
bytecode: DUMMY_BYTECODE,
},
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NULL_BYTES32],
expectedReturnStatus: false,
expectedReturnValues: [
REVERT_FLAGS.STATIC_VIOLATION,
'0x',
867484476,
2906,
expectedReturnValue: {
flag: REVERT_FLAGS.STATIC_VIOLATION,
nuisanceGasLeft: OVM_TX_GAS_LIMIT / 2,
},
},
],
},
{
functionName: 'ovmCREATE',
functionParams: [DUMMY_BYTECODE, false, []],
expectedReturnStatus: false,
expectedReturnValues: [
REVERT_FLAGS.STATIC_VIOLATION,
'0x',
867484476,
2906,
},
],
},
{
name: 'ovmCALL(ADDRESS_1) => ovmSTATICCALL(ADDRESS_2) => ovmCALLER',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSTATICCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmCALLER',
expectedReturnValue: '$DUMMY_OVM_ADDRESS_1',
},
],
},
expectedReturnStatus: true,
},
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
{
name: 'ovmCALL(ADDRESS_1) => ovmSTATICCALL(ADDRESS_2) => ovmADDRESS',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSTATICCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmADDRESS',
expectedReturnValue: '$DUMMY_OVM_ADDRESS_2',
},
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
{
name:
'ovmCALL(ADDRESS_1) => ovmSTATICCALL(ADDRESS_2) => ovmCALL(ADDRESS_3) => ovmSSTORE',
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_1',
[
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT / 2,
'$DUMMY_OVM_ADDRESS_2',
[
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmSTATICCALL',
functionParams: [GAS_LIMIT, '$DUMMY_OVM_ADDRESS_2', []],
expectedReturnStatus: true,
expectedReturnValues: [],
},
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_3',
subSteps: [
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NULL_BYTES32],
functionParams: {
key: NULL_BYTES32,
value: NULL_BYTES32,
},
expectedReturnStatus: false,
expectedReturnValues: [
REVERT_FLAGS.STATIC_VIOLATION,
'0x',
GAS_LIMIT / 2,
33806,
],
expectedReturnValue: {
flag: REVERT_FLAGS.STATIC_VIOLATION,
nuisanceGasLeft: OVM_TX_GAS_LIMIT / 2,
},
},
],
],
expectedReturnStatus: true,
expectedReturnValues: [],
},
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.STATIC_VIOLATION,
nuisanceGasLeft: OVM_TX_GAS_LIMIT / 2,
},
},
],
},
expectedReturnStatus: false,
},
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
{
name:
'ovmCALL(ADDRESS_1) => ovmSTATICCALL(ADDRESS_2) => ovmCALL(ADDRESS_3) => ovmSLOAD',
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_1',
[
{
functionName: 'ovmSTATICCALLToRevert',
functionParams: [
GAS_LIMIT / 2,
'$DUMMY_OVM_ADDRESS_2',
[REVERT_FLAGS.STATIC_VIOLATION, '0x', GAS_LIMIT / 2, 0],
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSTATICCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_3',
subSteps: [
{
functionName: 'ovmSLOAD',
functionParams: {
key: NON_NULL_BYTES32,
},
expectedReturnStatus: true,
expectedReturnValue: NULL_BYTES32,
},
],
},
expectedReturnStatus: true,
expectedReturnValues: [false, '0x'],
},
],
},
expectedReturnStatus: true,
},
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
{
name:
'ovmCALL(ADDRESS_1) => ovmSTATICCALL(ADDRESS_2) => ovmCALL(ADDRESS_3) => ovmCREATE',
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_1',
[
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT,
'$DUMMY_OVM_ADDRESS_1',
[
{
functionName: 'ovmSTATICCALLToRevert',
functionParams: [
GAS_LIMIT / 2,
'$DUMMY_OVM_ADDRESS_2',
[REVERT_FLAGS.STATIC_VIOLATION, '0x', GAS_LIMIT / 2, 0],
],
expectedReturnStatus: true,
expectedReturnValues: [false, '0x'],
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT / 2,
target: '$DUMMY_OVM_ADDRESS_3',
subSteps: [
{
functionName: 'ovmCREATE',
functionParams: {
bytecode: DUMMY_BYTECODE,
},
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.STATIC_VIOLATION,
nuisanceGasLeft: OVM_TX_GAS_LIMIT / 2,
},
},
],
],
expectedReturnStatus: true,
expectedReturnValues: [],
},
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.STATIC_VIOLATION,
nuisanceGasLeft: OVM_TX_GAS_LIMIT / 2,
},
},
],
},
expectedReturnStatus: false,
},
],
},
expectedReturnStatus: true,
expectedReturnValues: [],
},
],
},
],
}
runExecutionManagerTest(test_ovmSTATICCALL)
const runner = new ExecutionManagerTestRunner()
runner.run(test_ovmSTATICCALL)
......@@ -2,15 +2,12 @@
import {
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
OVM_TX_GAS_LIMIT,
NON_NULL_BYTES32,
REVERT_FLAGS,
ZERO_ADDRESS,
VERIFIED_EMPTY_CONTRACT_HASH,
} from '../../../../helpers'
const DUMMY_REVERT_DATA =
'0xdeadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420'
const GAS_METADATA_ADDRESS = '0x06a506a506a506a506a506a506a506a506a506a5'
enum GasMetadataKey {
......@@ -32,7 +29,7 @@ const test_run: TestDefinition = {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
nuisanceGasLeft: OVM_TX_GAS_LIMIT,
},
},
StateManager: {
......@@ -74,7 +71,6 @@ const test_run: TestDefinition = {
parameters: [
{
name: 'run => ovmCALL(ADDRESS_1) => ovmADDRESS',
focus: true,
steps: [
{
functionName: 'run',
......@@ -84,12 +80,12 @@ const test_run: TestDefinition = {
entrypoint: '$OVM_CALL_HELPER',
origin: ZERO_ADDRESS,
msgSender: ZERO_ADDRESS,
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
......
......@@ -5,7 +5,7 @@ import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
/* Internal Imports */
import { SAFETY_CHECKER_TEST_JSON } from '../../../helpers'
import { SAFETY_CHECKER_TEST_JSON } from '../../../data'
describe('OVM_SafetyChecker', () => {
let OVM_SafetyChecker: Contract
......
......@@ -2,12 +2,25 @@ import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, ContractFactory } from 'ethers'
import { Contract, ContractFactory, Signer, BigNumber } from 'ethers'
import _ from 'lodash'
/* Internal Imports */
import { DUMMY_ACCOUNTS, DUMMY_BYTES32, toOVMAccount } from '../../../helpers'
import { DUMMY_ACCOUNTS, DUMMY_BYTES32, ZERO_ADDRESS } from '../../../helpers'
const EMPTY_ACCOUNT_CODE_HASH =
'0x00004B1DC0DE000000004B1DC0DE000000004B1DC0DE000000004B1DC0DE0000'
const KECCAK_256_NULL =
'0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
describe('OVM_StateManager', () => {
let signer1: Signer
let signer2: Signer
let signer3: Signer
before(async () => {
;[signer1, signer2, signer3] = await ethers.getSigners()
})
let Factory__OVM_StateManager: ContractFactory
before(async () => {
Factory__OVM_StateManager = await ethers.getContractFactory(
......@@ -17,7 +30,65 @@ describe('OVM_StateManager', () => {
let OVM_StateManager: Contract
beforeEach(async () => {
OVM_StateManager = await Factory__OVM_StateManager.deploy()
OVM_StateManager = (
await Factory__OVM_StateManager.deploy(await signer1.getAddress())
).connect(signer1)
})
describe('setExecutionManager', () => {
describe('when called by the current owner', () => {
beforeEach(async () => {
OVM_StateManager = OVM_StateManager.connect(signer1)
})
it('should change the current OVM_ExecutionManager', async () => {
await expect(
OVM_StateManager.connect(signer1).setExecutionManager(
await signer2.getAddress()
)
).to.not.be.reverted
expect(await OVM_StateManager.ovmExecutionManager()).to.equal(
await signer2.getAddress()
)
})
})
describe('when called by the current OVM_ExecutionManager', () => {
beforeEach(async () => {
await OVM_StateManager.connect(signer1).setExecutionManager(
await signer2.getAddress()
)
})
it('should change the current OVM_ExecutionManager', async () => {
await expect(
OVM_StateManager.connect(signer2).setExecutionManager(
await signer3.getAddress()
)
).to.not.be.reverted
expect(await OVM_StateManager.ovmExecutionManager()).to.equal(
await signer3.getAddress()
)
})
})
describe('when called by any other account', () => {
beforeEach(async () => {
OVM_StateManager = OVM_StateManager.connect(signer1)
})
it('should revert', async () => {
await expect(
OVM_StateManager.connect(signer3).setExecutionManager(
await signer3.getAddress()
)
).to.be.revertedWith(
'Function can only be called by authenticated addresses'
)
})
})
})
describe('putAccount', () => {
......@@ -53,12 +124,12 @@ describe('OVM_StateManager', () => {
)
expect(
toOVMAccount(
_.toPlainObject(
await OVM_StateManager.callStatic.getAccount(
DUMMY_ACCOUNTS[0].address
)
)
).to.deep.equal(DUMMY_ACCOUNTS[0].data)
).to.deep.include(DUMMY_ACCOUNTS[0].data)
})
it('should be able to retrieve an overwritten OVM account', async () => {
......@@ -73,97 +144,837 @@ describe('OVM_StateManager', () => {
)
expect(
toOVMAccount(
_.toPlainObject(
await OVM_StateManager.callStatic.getAccount(
DUMMY_ACCOUNTS[0].address
)
)
).to.deep.equal(DUMMY_ACCOUNTS[1].data)
).to.deep.include(DUMMY_ACCOUNTS[1].data)
})
})
describe('hasAccount', () => {
it('should return true if an account exists', async () => {
describe('when the account exists', () => {
beforeEach(async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNTS[0].address,
DUMMY_ACCOUNTS[0].data
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.hasAccount(DUMMY_ACCOUNTS[0].address)
await OVM_StateManager.callStatic.hasAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(true)
})
})
it('should return false if the account does not exist', async () => {
describe('when the account does not exist', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.hasAccount(DUMMY_ACCOUNTS[0].address)
await OVM_StateManager.callStatic.hasAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
})
describe('putContractStorage', () => {
it('should be able to insert a storage slot for a given contract', async () => {
await expect(
OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
describe('hasEmptyAccount', () => {
describe('when the account has the EMPTY_ACCOUNT_CODE_HASH', () => {
beforeEach(async () => {
await OVM_StateManager.putAccount(DUMMY_ACCOUNTS[0].address, {
...DUMMY_ACCOUNTS[0].data,
codeHash: EMPTY_ACCOUNT_CODE_HASH,
})
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.hasEmptyAccount(
DUMMY_ACCOUNTS[0].address
)
).to.not.be.reverted
).to.equal(true)
})
})
it('should be able to overwrite a storage slot for a given contract', async () => {
await OVM_StateManager.putContractStorage(
describe('when the account has a different non-zero codehash', () => {
beforeEach(async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
DUMMY_ACCOUNTS[0].data
)
})
await expect(
OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[2]
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.hasEmptyAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
describe('when the account does not exist', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.hasEmptyAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
})
describe('setAccountNonce', () => {
it('should change the account nonce', async () => {
await expect(
OVM_StateManager.setAccountNonce(DUMMY_ACCOUNTS[0].address, 1234)
).to.not.be.reverted
})
})
describe('getContractStorage', () => {
it('should be able to retrieve a storage slot for a given contract', async () => {
await OVM_StateManager.putContractStorage(
describe('getAccountNonce', () => {
describe('when the account exists', () => {
beforeEach(async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
DUMMY_ACCOUNTS[0].data
)
})
it('should return the current nonce', async () => {
expect(
await OVM_StateManager.callStatic.getContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
await OVM_StateManager.callStatic.getAccountNonce(
DUMMY_ACCOUNTS[0].address
)
).to.equal(DUMMY_BYTES32[1])
).to.equal(DUMMY_ACCOUNTS[0].data.nonce)
})
it('should be able to retrieve an overwritten storage slot for a given contract', async () => {
await OVM_StateManager.putContractStorage(
describe('when the nonce has been modified', () => {
beforeEach(async () => {
await OVM_StateManager.setAccountNonce(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
1234
)
})
await OVM_StateManager.putContractStorage(
it('should return the updated nonce', async () => {
expect(
await OVM_StateManager.callStatic.getAccountNonce(
DUMMY_ACCOUNTS[0].address
)
).to.equal(1234)
})
})
})
describe('when the account does not exist', () => {
it('should return zero', async () => {
expect(
await OVM_StateManager.callStatic.getAccountNonce(
DUMMY_ACCOUNTS[0].address
)
).to.equal(0)
})
})
})
describe('getAccountEthAddress', () => {
describe('when the account exists', () => {
beforeEach(async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[2]
DUMMY_ACCOUNTS[0].data
)
})
it('should return the account eth address', async () => {
expect(
await OVM_StateManager.callStatic.getContractStorage(
await OVM_StateManager.getAccountEthAddress(DUMMY_ACCOUNTS[0].address)
).to.equal(DUMMY_ACCOUNTS[0].data.ethAddress)
})
})
describe('when the account does not exist', () => {
it('should return the zero address', async () => {
expect(
await OVM_StateManager.getAccountEthAddress(DUMMY_ACCOUNTS[0].address)
).to.equal(ZERO_ADDRESS)
})
})
})
describe('initPendingAccount', () => {
it('should set the initial account values', async () => {
await expect(
OVM_StateManager.initPendingAccount(DUMMY_ACCOUNTS[0].address)
).to.not.be.reverted
expect(
_.toPlainObject(
await OVM_StateManager.callStatic.getAccount(
DUMMY_ACCOUNTS[0].address
)
)
).to.deep.include({
nonce: BigNumber.from(1),
codeHash: KECCAK_256_NULL,
isFresh: true,
})
})
})
describe('commitPendingAccount', () => {
it('should set the remaining account values', async () => {
await expect(
OVM_StateManager.commitPendingAccount(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
DUMMY_ACCOUNTS[0].data.ethAddress,
DUMMY_ACCOUNTS[0].data.codeHash
)
).to.equal(DUMMY_BYTES32[2])
).to.not.be.reverted
expect(
_.toPlainObject(
await OVM_StateManager.callStatic.getAccount(
DUMMY_ACCOUNTS[0].address
)
)
).to.deep.include({
ethAddress: DUMMY_ACCOUNTS[0].data.ethAddress,
codeHash: DUMMY_ACCOUNTS[0].data.codeHash,
})
})
})
describe('testAndSetAccountChanged', () => {
describe('when the account has not yet been changed', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
describe('when the account has been changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
).to.equal(true)
})
})
})
describe('testAndSetAccountLoaded', () => {
describe('when the account has not yet been loaded', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetAccountLoaded(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
describe('when the account has been loaded', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountLoaded(
DUMMY_ACCOUNTS[0].address
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetAccountLoaded(
DUMMY_ACCOUNTS[0].address
)
).to.equal(true)
})
})
describe('when the account has been changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetAccountLoaded(
DUMMY_ACCOUNTS[0].address
)
).to.equal(true)
})
})
})
describe('commitAccount', () => {
describe('when the account has not been touched', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.commitAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
describe('when the account has been loaded but not changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountLoaded(
DUMMY_ACCOUNTS[0].address
)
})
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.commitAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
describe('when the account has been changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.commitAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(true)
})
})
describe('when the account has already been committed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
await OVM_StateManager.commitAccount(DUMMY_ACCOUNTS[0].address)
})
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.commitAccount(
DUMMY_ACCOUNTS[0].address
)
).to.equal(false)
})
})
})
describe('incrementTotalUncommittedAccounts', () => {
it('should update the total uncommitted accounts', async () => {
await expect(OVM_StateManager.incrementTotalUncommittedAccounts()).to.not
.be.reverted
})
})
describe('getTotalUncommittedAccounts', () => {
describe('when the total count has not been changed', () => {
it('should return zero', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedAccounts()
).to.equal(0)
})
})
describe('when the count has been incremented', () => {
describe('one time', () => {
beforeEach(async () => {
await OVM_StateManager.incrementTotalUncommittedAccounts()
})
it('should return one', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedAccounts()
).to.equal(1)
})
describe('when an account has been committed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
await OVM_StateManager.commitAccount(DUMMY_ACCOUNTS[0].address)
})
it('should return zero', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedAccounts()
).to.equal(0)
})
})
})
describe('ten times', () => {
beforeEach(async () => {
for (let i = 0; i < 10; i++) {
await OVM_StateManager.incrementTotalUncommittedAccounts()
}
})
it('should return one', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedAccounts()
).to.equal(10)
})
describe('when an account has been committed', () => {
describe('one time', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetAccountChanged(
DUMMY_ACCOUNTS[0].address
)
await OVM_StateManager.commitAccount(DUMMY_ACCOUNTS[0].address)
})
it('should return nine', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedAccounts()
).to.equal(9)
})
})
})
})
})
})
describe('putContractStorage', () => {
it('should be able to insert a storage slot for a given contract', async () => {
await expect(
OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
)
).to.not.be.reverted
})
it('should be able to overwrite a storage slot for a given contract', async () => {
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
)
await expect(
OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[2]
)
).to.not.be.reverted
})
})
describe('getContractStorage', () => {
it('should be able to retrieve a storage slot for a given contract', async () => {
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
)
expect(
await OVM_StateManager.callStatic.getContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(DUMMY_BYTES32[1])
})
it('should be able to retrieve an overwritten storage slot for a given contract', async () => {
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
)
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[2]
)
expect(
await OVM_StateManager.callStatic.getContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(DUMMY_BYTES32[2])
})
})
describe('hasContractStorage', () => {
describe('when the storage slot has not been verified', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.hasContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(false)
})
})
describe('when the storage slot has been verified', () => {
beforeEach(async () => {
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0],
DUMMY_BYTES32[1]
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.hasContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(true)
})
})
describe('when the account is newly created', () => {
beforeEach(async () => {
await OVM_StateManager.initPendingAccount(DUMMY_ACCOUNTS[0].address)
})
it('should return true for any slot', async () => {
for (const DUMMY_KEY of DUMMY_BYTES32) {
expect(
await OVM_StateManager.hasContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_KEY
)
).to.equal(true)
}
})
})
})
describe('testAndSetContractStorageChanged', () => {
describe('when the storage slot has not been touched', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(false)
})
})
describe('when the storage slot has been loaded but not changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageLoaded(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(false)
})
})
describe('when the storage slot has been changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(true)
})
})
describe('when the storage slot has been committed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
await OVM_StateManager.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(true)
})
})
})
describe('testAndSetContractStorageLoaded', () => {
describe('when the storage slot has not been touched', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageLoaded(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(false)
})
})
describe('when the storage slot has already been loaded', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageLoaded(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageLoaded(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(true)
})
})
describe('when the storage slot has already been changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageLoaded(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(true)
})
})
describe('when the storage slot has been committed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
await OVM_StateManager.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.testAndSetContractStorageLoaded(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(true)
})
})
})
describe('commitContractStorage', () => {
describe('when the storage slot has not been touched', () => {
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(false)
})
})
describe('when the storage slot has been loaded', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageLoaded(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(false)
})
})
describe('when the storage slot has been changed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return true', async () => {
expect(
await OVM_StateManager.callStatic.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(true)
})
})
describe('when the storage slot has been committed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
await OVM_StateManager.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return false', async () => {
expect(
await OVM_StateManager.callStatic.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
).to.equal(false)
})
})
})
describe('incrementTotalUncommittedContractStorage', () => {
it('should update the total uncommitted storage slots', async () => {
await expect(OVM_StateManager.incrementTotalUncommittedContractStorage())
.to.not.be.reverted
})
})
describe('getTotalUncommittedContractStorage', () => {
describe('when the total count has not been changed', () => {
it('should return zero', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedContractStorage()
).to.equal(0)
})
})
describe('when the count has been incremented', () => {
describe('one time', () => {
beforeEach(async () => {
await OVM_StateManager.incrementTotalUncommittedContractStorage()
})
it('should return one', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedContractStorage()
).to.equal(1)
})
describe('when a storage slot has been committed', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
await OVM_StateManager.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return zero', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedContractStorage()
).to.equal(0)
})
})
})
describe('ten times', () => {
beforeEach(async () => {
for (let i = 0; i < 10; i++) {
await OVM_StateManager.incrementTotalUncommittedContractStorage()
}
})
it('should return ten', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedContractStorage()
).to.equal(10)
})
describe('when a storage slot has been committed', () => {
describe('one time', () => {
beforeEach(async () => {
await OVM_StateManager.testAndSetContractStorageChanged(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
await OVM_StateManager.commitContractStorage(
DUMMY_ACCOUNTS[0].address,
DUMMY_BYTES32[0]
)
})
it('should return nine', async () => {
expect(
await OVM_StateManager.callStatic.getTotalUncommittedContractStorage()
).to.equal(9)
})
})
})
})
})
})
})
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, ContractFactory, Signer } from 'ethers'
/* Internal Imports */
import { getProxyManager, ZERO_ADDRESS, NULL_BYTES32 } from '../../../helpers'
const parseQueueElement = (result: any[]): any => {
return {
timestamp: result[0].toNumber(),
batchRoot: result[1],
isL1ToL2Batch: result[2],
}
}
const makeQueueElements = (count: number): any => {
const elements = []
for (let i = 0; i < count; i++) {
elements.push({
timestamp: Date.now(),
batchRoot: NULL_BYTES32,
isL1ToL2Batch: false,
})
}
return elements
}
describe('OVM_L1ToL2TransactionQueue', () => {
let signer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
})
let Proxy_Manager: Contract
before(async () => {
Proxy_Manager = await getProxyManager()
})
let Factory__OVM_L1ToL2TransactionQueue: ContractFactory
before(async () => {
Factory__OVM_L1ToL2TransactionQueue = await ethers.getContractFactory(
'OVM_L1ToL2TransactionQueue'
)
})
let OVM_L1ToL2TransactionQueue: Contract
beforeEach(async () => {
OVM_L1ToL2TransactionQueue = await Factory__OVM_L1ToL2TransactionQueue.deploy(
Proxy_Manager.address
)
})
describe('enqueue()', () => {
it('should allow users to enqueue an element', async () => {
const [element] = makeQueueElements(1)
await expect(OVM_L1ToL2TransactionQueue.enqueue(element)).to.not.be
.reverted
})
it('should allow users to enqueue more than one element', async () => {
const elements = makeQueueElements(10)
for (const element of elements) {
await expect(OVM_L1ToL2TransactionQueue.enqueue(element)).to.not.be
.reverted
}
})
})
describe('dequeue()', () => {
describe('when the sender is not the OVM_CanonicalTransactionChain', () => {
before(async () => {
await Proxy_Manager.setProxy(
'OVM_CanonicalTransactionChain',
ZERO_ADDRESS
)
})
it('should revert', async () => {
await expect(OVM_L1ToL2TransactionQueue.dequeue()).to.be.revertedWith(
'Sender is not allowed to enqueue.'
)
})
})
describe('when the sender is the OVM_CanonicalTransactionChain', () => {
before(async () => {
await Proxy_Manager.setProxy(
'OVM_CanonicalTransactionChain',
await signer.getAddress()
)
})
it('should revert if the queue is empty', async () => {
await expect(OVM_L1ToL2TransactionQueue.dequeue()).to.be.revertedWith(
'Queue is empty.'
)
})
it('should allow users to dequeue an element', async () => {
const [element] = makeQueueElements(1)
await OVM_L1ToL2TransactionQueue.enqueue(element)
await expect(OVM_L1ToL2TransactionQueue.dequeue()).to.not.be.reverted
})
it('should allow users to dequeue more than one element', async () => {
const elements = makeQueueElements(10)
for (const element of elements) {
await OVM_L1ToL2TransactionQueue.enqueue(element)
}
for (const element of elements) {
await expect(OVM_L1ToL2TransactionQueue.dequeue()).to.not.be.reverted
}
})
})
})
describe('size()', () => {
before(async () => {
await Proxy_Manager.setProxy(
'OVM_CanonicalTransactionChain',
await signer.getAddress()
)
})
it('should return zero when no elements are in the queue', async () => {
const size = await OVM_L1ToL2TransactionQueue.size()
expect(size).to.equal(0)
})
it('should increase when new elements are enqueued', async () => {
const elements = makeQueueElements(10)
for (let i = 0; i < elements.length; i++) {
await OVM_L1ToL2TransactionQueue.enqueue(elements[i])
const size = await OVM_L1ToL2TransactionQueue.size()
expect(size).to.equal(i + 1)
}
})
it('should decrease when elements are dequeued', async () => {
const elements = makeQueueElements(10)
for (const element of elements) {
await OVM_L1ToL2TransactionQueue.enqueue(element)
}
for (let i = 0; i < elements.length; i++) {
await OVM_L1ToL2TransactionQueue.dequeue()
const size = await OVM_L1ToL2TransactionQueue.size()
expect(size).to.equal(elements.length - i - 1)
}
})
})
describe('peek()', () => {
before(async () => {
await Proxy_Manager.setProxy(
'OVM_CanonicalTransactionChain',
await signer.getAddress()
)
})
it('should revert when the queue is empty', async () => {
await expect(OVM_L1ToL2TransactionQueue.peek()).to.be.revertedWith(
'Queue is empty.'
)
})
it('should return the front element if only one exists', async () => {
const [element] = makeQueueElements(1)
await OVM_L1ToL2TransactionQueue.enqueue(element)
const front = await OVM_L1ToL2TransactionQueue.peek()
expect(parseQueueElement(front)).to.deep.equal(element)
})
it('should return the front if more than one exists', async () => {
const elements = makeQueueElements(10)
for (const element of elements) {
await OVM_L1ToL2TransactionQueue.enqueue(element)
const front = await OVM_L1ToL2TransactionQueue.peek()
expect(parseQueueElement(front)).to.deep.equal(elements[0])
}
})
it('should return the new front when elements are dequeued', async () => {
const elements = makeQueueElements(10)
for (const element of elements) {
await OVM_L1ToL2TransactionQueue.enqueue(elements)
}
for (let i = 0; i < elements.length - 1; i++) {
const front = await OVM_L1ToL2TransactionQueue.peek()
expect(parseQueueElement(front)).to.deep.equal(elements[i + 1])
await OVM_L1ToL2TransactionQueue.dequeue()
}
})
})
})
......@@ -2,46 +2,24 @@ import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { ContractFactory, Contract, Signer, BigNumber } from 'ethers'
import { ContractFactory, Contract, BigNumber } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
/* Internal Imports */
import {
getProxyManager,
getMockContract,
MockContract,
ZERO_ADDRESS,
NULL_BYTES32,
NON_NULL_BYTES32,
makeAddressManager,
setProxyTarget,
DUMMY_BATCH_HEADERS,
DUMMY_BATCH_PROOFS,
DUMMY_OVM_TRANSACTIONS,
NON_NULL_BYTES32,
NULL_BYTES32,
} from '../../../helpers'
const DUMMY_BATCH_HEADER = {
batchIndex: 0,
batchRoot: NULL_BYTES32,
batchSize: 0,
prevTotalElements: 0,
extraData: NULL_BYTES32,
}
const DUMMY_BATCH_PROOF = {
index: 0,
siblings: [NULL_BYTES32],
}
const DUMMY_OVM_TRANSACTION = {
timestamp: 0,
queueOrigin: 0,
entrypoint: ZERO_ADDRESS,
origin: ZERO_ADDRESS,
msgSender: ZERO_ADDRESS,
gasLimit: 0,
data: NULL_BYTES32,
}
describe('OVM_FraudVerifier', () => {
let Proxy_Manager: Contract
let AddressManager: Contract
before(async () => {
Proxy_Manager = await getProxyManager()
AddressManager = await makeAddressManager()
})
let Mock__OVM_StateCommitmentChain: MockContract
......@@ -49,49 +27,43 @@ describe('OVM_FraudVerifier', () => {
let Mock__OVM_StateTransitioner: MockContract
let Mock__OVM_StateTransitionerFactory: MockContract
before(async () => {
Mock__OVM_StateCommitmentChain = await getMockContract(
Mock__OVM_StateCommitmentChain = smockit(
await ethers.getContractFactory('OVM_StateCommitmentChain')
)
Mock__OVM_CanonicalTransactionChain = await getMockContract(
Mock__OVM_CanonicalTransactionChain = smockit(
await ethers.getContractFactory('OVM_CanonicalTransactionChain')
)
Mock__OVM_StateTransitioner = await getMockContract(
Mock__OVM_StateTransitioner = smockit(
await ethers.getContractFactory('OVM_StateTransitioner')
)
Mock__OVM_StateTransitionerFactory = await getMockContract(
Mock__OVM_StateTransitionerFactory = smockit(
await ethers.getContractFactory('OVM_StateTransitionerFactory')
)
await setProxyTarget(
Proxy_Manager,
AddressManager,
'OVM_StateCommitmentChain',
Mock__OVM_StateCommitmentChain
)
await setProxyTarget(
Proxy_Manager,
AddressManager,
'OVM_CanonicalTransactionChain',
Mock__OVM_CanonicalTransactionChain
)
await setProxyTarget(
Proxy_Manager,
'OVM_StateTransitioner',
Mock__OVM_StateTransitioner
)
await setProxyTarget(
Proxy_Manager,
AddressManager,
'OVM_StateTransitionerFactory',
Mock__OVM_StateTransitionerFactory
)
Mock__OVM_StateTransitionerFactory.setReturnValues('create', [
Mock__OVM_StateTransitioner.address,
])
Mock__OVM_StateTransitionerFactory.smocked.create.will.return.with(
Mock__OVM_StateTransitioner.address
)
})
let Factory__OVM_FraudVerifier: ContractFactory
......@@ -104,25 +76,27 @@ describe('OVM_FraudVerifier', () => {
let OVM_FraudVerifier: Contract
beforeEach(async () => {
OVM_FraudVerifier = await Factory__OVM_FraudVerifier.deploy(
Proxy_Manager.address
AddressManager.address
)
})
describe('initializeFraudVerification', () => {
describe('when provided an invalid pre-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.setReturnValues('verifyElement', [false])
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
false
)
})
it('should revert', async () => {
await expect(
OVM_FraudVerifier.initializeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_OVM_TRANSACTION,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
).to.be.revertedWith('Invalid pre-state root inclusion proof.')
})
......@@ -130,25 +104,27 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid pre-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.setReturnValues('verifyElement', [true])
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
true
)
})
describe('when provided an invalid transaction inclusion proof', () => {
before(() => {
Mock__OVM_CanonicalTransactionChain.setReturnValues('verifyElement', [
false,
])
Mock__OVM_CanonicalTransactionChain.smocked.verifyElement.will.return.with(
false
)
})
it('should revert', async () => {
await expect(
OVM_FraudVerifier.initializeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_OVM_TRANSACTION,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
).to.be.revertedWith('Invalid transaction inclusion proof.')
})
......@@ -156,20 +132,20 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid transaction inclusion proof', () => {
before(() => {
Mock__OVM_CanonicalTransactionChain.setReturnValues('verifyElement', [
true,
])
Mock__OVM_CanonicalTransactionChain.smocked.verifyElement.will.return.with(
true
)
})
it('should deploy a new state transitioner', async () => {
await expect(
OVM_FraudVerifier.initializeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_OVM_TRANSACTION,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
).to.not.be.reverted
......@@ -183,34 +159,37 @@ describe('OVM_FraudVerifier', () => {
describe('finalizeFraudVerification', () => {
beforeEach(async () => {
Mock__OVM_StateCommitmentChain.setReturnValues('verifyElement', [true])
Mock__OVM_CanonicalTransactionChain.setReturnValues('verifyElement', [
true,
])
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
true
)
Mock__OVM_CanonicalTransactionChain.smocked.verifyElement.will.return.with(
true
)
await OVM_FraudVerifier.initializeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_OVM_TRANSACTION,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
})
describe('when the transition process is not complete', () => {
before(() => {
Mock__OVM_StateTransitioner.setReturnValues('isComplete', [false])
before(async () => {
Mock__OVM_StateTransitioner.smocked.isComplete.will.return.with(false)
})
it('should revert', async () => {
await expect(
OVM_FraudVerifier.finalizeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
NON_NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
).to.be.revertedWith(
'State transition process must be completed prior to finalization.'
......@@ -220,23 +199,23 @@ describe('OVM_FraudVerifier', () => {
describe('when the transition process is complete', () => {
before(() => {
Mock__OVM_StateTransitioner.setReturnValues('isComplete', [true])
Mock__OVM_StateTransitioner.smocked.isComplete.will.return.with(true)
})
describe('when provided an invalid post-state root index', () => {
const batchProof = {
...DUMMY_BATCH_PROOF,
index: DUMMY_BATCH_PROOF.index + 2,
...DUMMY_BATCH_PROOFS[0],
index: DUMMY_BATCH_PROOFS[0].index + 2,
}
it('should revert', async () => {
await expect(
OVM_FraudVerifier.finalizeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
NON_NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_HEADERS[0],
batchProof
)
).to.be.revertedWith('Invalid post-state root index.')
......@@ -245,25 +224,25 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid post-state root index', () => {
const batchProof = {
...DUMMY_BATCH_PROOF,
index: DUMMY_BATCH_PROOF.index + 1,
...DUMMY_BATCH_PROOFS[0],
index: DUMMY_BATCH_PROOFS[0].index + 1,
}
describe('when provided an invalid pre-state root inclusion proof', () => {
beforeEach(() => {
Mock__OVM_StateCommitmentChain.setReturnValues('verifyElement', [
false,
])
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
false
)
})
it('should revert', async () => {
await expect(
OVM_FraudVerifier.finalizeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
NON_NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_HEADERS[0],
batchProof
)
).to.be.revertedWith('Invalid pre-state root inclusion proof.')
......@@ -272,17 +251,16 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid pre-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.setReturnValues('verifyElement', [
true,
])
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
true
)
})
describe('when provided an invalid post-state root inclusion proof', () => {
beforeEach(() => {
Mock__OVM_StateCommitmentChain.setReturnValues(
'verifyElement',
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
(stateRoot: string, ...args: any) => {
return [stateRoot !== NON_NULL_BYTES32]
return stateRoot !== NON_NULL_BYTES32
}
)
})
......@@ -291,10 +269,10 @@ describe('OVM_FraudVerifier', () => {
await expect(
OVM_FraudVerifier.finalizeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
NON_NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_HEADERS[0],
batchProof
)
).to.be.revertedWith('Invalid post-state root inclusion proof.')
......@@ -303,16 +281,15 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid post-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.setReturnValues('verifyElement', [
true,
])
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
true
)
})
describe('when the provided post-state root does not differ from the computed one', () => {
before(() => {
Mock__OVM_StateTransitioner.setReturnValues(
'getPostStateRoot',
[NON_NULL_BYTES32]
Mock__OVM_StateTransitioner.smocked.getPostStateRoot.will.return.with(
NON_NULL_BYTES32
)
})
......@@ -320,10 +297,10 @@ describe('OVM_FraudVerifier', () => {
await expect(
OVM_FraudVerifier.finalizeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
NON_NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_HEADERS[0],
batchProof
)
).to.be.revertedWith(
......@@ -334,29 +311,26 @@ describe('OVM_FraudVerifier', () => {
describe('when the provided post-state root differs from the computed one', () => {
before(() => {
Mock__OVM_StateTransitioner.setReturnValues(
'getPostStateRoot',
[NULL_BYTES32]
Mock__OVM_StateTransitioner.smocked.getPostStateRoot.will.return.with(
NULL_BYTES32
)
})
it('should succeed and attempt to delete a state batch', async () => {
await OVM_FraudVerifier.finalizeFraudVerification(
NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_PROOF,
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
NON_NULL_BYTES32,
DUMMY_BATCH_HEADER,
DUMMY_BATCH_HEADERS[0],
batchProof
)
expect(
Mock__OVM_StateCommitmentChain.getCallData(
'deleteStateBatch',
0
)
Mock__OVM_StateCommitmentChain.smocked.deleteStateBatch
.calls[0]
).to.deep.equal([
Object.values(DUMMY_BATCH_HEADER).map((value) => {
Object.values(DUMMY_BATCH_HEADERS[0]).map((value) => {
return Number.isInteger(value)
? BigNumber.from(value)
: value
......
/* tslint:disable:no-empty */
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { BigNumber, Contract, ContractFactory } from 'ethers'
/* Internal Imports */
import {
makeAddressManager,
NON_NULL_BYTES32,
NON_ZERO_ADDRESS,
NULL_BYTES32,
setProxyTarget,
TrieTestGenerator,
ZERO_ADDRESS,
} from '../../../helpers'
import {
MockContract,
smockit,
ModifiableContract,
smoddit,
ModifiableContractFactory,
} from '@eth-optimism/smock'
import { keccak256 } from 'ethers/lib/utils'
describe('OVM_StateTransitioner', () => {
let AddressManager: Contract
before(async () => {
AddressManager = await makeAddressManager()
})
let Mock__OVM_ExecutionManager: MockContract
let Mock__OVM_StateManagerFactory: MockContract
let Mock__OVM_StateManager: MockContract
before(async () => {
Mock__OVM_ExecutionManager = smockit(
await ethers.getContractFactory('OVM_ExecutionManager')
)
Mock__OVM_StateManagerFactory = smockit(
await ethers.getContractFactory('OVM_StateManagerFactory')
)
Mock__OVM_StateManager = smockit(
await ethers.getContractFactory('OVM_StateManager')
)
await setProxyTarget(
AddressManager,
'OVM_ExecutionManager',
Mock__OVM_ExecutionManager
)
await setProxyTarget(
AddressManager,
'OVM_StateManagerFactory',
Mock__OVM_StateManagerFactory
)
Mock__OVM_StateManagerFactory.smocked.create.will.return.with(
Mock__OVM_StateManager.address
)
Mock__OVM_StateManager.smocked.putAccount.will.return()
})
let Factory__OVM_StateTransitioner: ModifiableContractFactory
before(async () => {
Factory__OVM_StateTransitioner = await smoddit('OVM_StateTransitioner')
})
let OVM_StateTransitioner: ModifiableContract
beforeEach(async () => {
OVM_StateTransitioner = await Factory__OVM_StateTransitioner.deploy(
AddressManager.address,
0,
NULL_BYTES32,
NULL_BYTES32
)
})
describe('proveContractState', () => {
let ovmContractAddress = NON_ZERO_ADDRESS
let ethContractAddress = ZERO_ADDRESS
let account: any
beforeEach(() => {
Mock__OVM_StateManager.smocked.hasAccount.will.return.with(false)
account = {
nonce: 0,
balance: 0,
storageRoot: NULL_BYTES32,
codeHash: NULL_BYTES32,
}
})
describe('when provided an invalid code hash', () => {
it('should revert', async () => {})
beforeEach(() => {
account.codeHash = NON_NULL_BYTES32
})
it('should revert', async () => {
await expect(
OVM_StateTransitioner.proveContractState(
ovmContractAddress,
ethContractAddress,
account,
'0x'
)
).to.be.revertedWith('Invalid code hash provided.')
})
})
describe('when provided a valid code hash', () => {
beforeEach(async () => {
ethContractAddress = OVM_StateTransitioner.address
account.codeHash = keccak256(
await ethers.provider.getCode(OVM_StateTransitioner.address)
)
})
describe('when provided an invalid account inclusion proof', () => {
it('should revert', async () => {})
const proof = '0x'
it('should revert', async () => {
await expect(
OVM_StateTransitioner.proveContractState(
ovmContractAddress,
ethContractAddress,
account,
proof
)
).to.be.reverted
})
})
describe('when provided a valid account inclusion proof', () => {
let proof: string
beforeEach(async () => {
const generator = await TrieTestGenerator.fromAccounts({
accounts: [
{
...account,
address: ovmContractAddress,
},
],
secure: true,
})
const test = await generator.makeAccountProofTest(ovmContractAddress)
proof = test.accountTrieWitness
OVM_StateTransitioner = await Factory__OVM_StateTransitioner.deploy(
AddressManager.address,
0,
test.accountTrieRoot,
NULL_BYTES32
)
})
describe('when provided a valid account inclusion proof', () => {})
it('should put the account in the state manager', async () => {
await expect(
OVM_StateTransitioner.proveContractState(
ovmContractAddress,
ethContractAddress,
account,
proof
)
).to.not.be.reverted
expect(
Mock__OVM_StateManager.smocked.putAccount.calls[0]
).to.deep.equal([
NON_ZERO_ADDRESS,
[
BigNumber.from(account.nonce),
BigNumber.from(account.balance),
account.storageRoot,
account.codeHash,
ethContractAddress,
false,
],
])
})
})
})
})
describe('proveStorageSlot', () => {
beforeEach(() => {
Mock__OVM_StateManager.smocked.hasContractStorage.will.return.with(false)
})
describe('when the corresponding account is not proven', () => {
it('should revert', async () => {})
beforeEach(() => {
Mock__OVM_StateManager.smocked.hasAccount.will.return.with(false)
})
it('should revert', async () => {
await expect(
OVM_StateTransitioner.proveStorageSlot(
NON_ZERO_ADDRESS,
NON_NULL_BYTES32,
NON_NULL_BYTES32,
'0x'
)
).to.be.revertedWith(
'Contract must be verified before proving a storage slot.'
)
})
})
describe('when the corresponding account is proven', () => {
beforeEach(() => {
Mock__OVM_StateManager.smocked.hasAccount.will.return.with(true)
})
describe('when provided an invalid slot inclusion proof', () => {
it('should revert', async () => {})
let key = keccak256('0x1234')
let val = keccak256('0x5678')
let proof = '0x'
beforeEach(async () => {
const generator = await TrieTestGenerator.fromNodes({
nodes: [
{
key,
val,
},
],
secure: true,
})
describe('when provided a valid slot inclusion proof', () => {})
const test = await generator.makeInclusionProofTest(0)
Mock__OVM_StateManager.smocked.getAccountStorageRoot.will.return.with(
test.root
)
})
it('should revert', async () => {
await expect(
OVM_StateTransitioner.proveStorageSlot(
ZERO_ADDRESS,
key,
val,
proof
)
).to.be.reverted
})
})
describe('when provided a valid slot inclusion proof', () => {
let key = keccak256('0x1234')
let val = keccak256('0x5678')
let proof: string
beforeEach(async () => {
const generator = await TrieTestGenerator.fromNodes({
nodes: [
{
key,
val,
},
],
secure: true,
})
const test = await generator.makeInclusionProofTest(0)
proof = test.proof
Mock__OVM_StateManager.smocked.getAccountStorageRoot.will.return.with(
test.root
)
})
it('should insert the storage slot', async () => {
await expect(
OVM_StateTransitioner.proveStorageSlot(
ZERO_ADDRESS,
key,
val,
proof
)
).to.not.be.reverted
expect(
Mock__OVM_StateManager.smocked.putContractStorage.calls[0]
).to.deep.equal([ZERO_ADDRESS, key, val])
})
})
})
})
......@@ -35,46 +294,280 @@ describe('OVM_StateTransitioner', () => {
})
describe('commitContractState', () => {
describe('when the account was not changed', () => {
it('should revert', async () => {})
beforeEach(async () => {
OVM_StateTransitioner.smodify.set({
phase: 1,
})
})
describe('when the account was changed', () => {
describe('when the account has not been committed', () => {
it('should commit the account and update the state', async () => {})
let ovmContractAddress = NON_ZERO_ADDRESS
let account: any
beforeEach(() => {
Mock__OVM_StateManager.smocked.hasAccount.will.return.with(false)
account = {
nonce: 0,
balance: 0,
storageRoot: NULL_BYTES32,
codeHash: NULL_BYTES32,
}
})
describe('when the account was already committed', () => {
it('should revert', () => {})
describe('when the account was not changed or has already been committed', () => {
before(() => {
Mock__OVM_StateManager.smocked.commitAccount.will.return.with(false)
})
it('should revert', async () => {
await expect(
OVM_StateTransitioner.commitContractState(
ovmContractAddress,
account,
'0x'
)
).to.be.revertedWith(
'Account was not changed or has already been committed.'
)
})
})
describe('when the account was changed or has not already been committed', () => {
before(() => {
Mock__OVM_StateManager.smocked.commitAccount.will.return.with(true)
})
describe('when given an valid update proof', () => {
let proof: string
let postStateRoot: string
beforeEach(async () => {
const generator = await TrieTestGenerator.fromAccounts({
accounts: [
{
...account,
nonce: 10,
address: ovmContractAddress,
},
],
secure: true,
})
const test = await generator.makeAccountUpdateTest(
ovmContractAddress,
account
)
proof = test.accountTrieWitness
postStateRoot = test.newAccountTrieRoot
OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot,
})
})
it('should update the post state root', async () => {
await expect(
OVM_StateTransitioner.commitContractState(
ovmContractAddress,
account,
proof
)
).to.not.be.reverted
expect(await OVM_StateTransitioner.getPostStateRoot()).to.equal(
postStateRoot
)
})
})
})
})
describe('commitStorageSlot', () => {
describe('when the slot was not changed', () => {
it('should revert', async () => {})
beforeEach(() => {
OVM_StateTransitioner.smodify.set({
phase: 1,
})
})
let ovmContractAddress = NON_ZERO_ADDRESS
let account: any
let key = keccak256('0x1234')
let val = keccak256('0x5678')
let newVal = keccak256('0x4321')
beforeEach(() => {
account = {
nonce: 0,
balance: 0,
storageRoot: NULL_BYTES32,
codeHash: NULL_BYTES32,
}
Mock__OVM_StateManager.smocked.getAccount.will.return.with({
...account,
ethAddress: ZERO_ADDRESS,
isFresh: false,
})
})
describe('when the slot was not changed or was already committed', () => {
beforeEach(() => {
Mock__OVM_StateManager.smocked.commitContractStorage.will.return.with(
false
)
})
it('should revert', async () => {
await expect(
OVM_StateTransitioner.commitStorageSlot(
ovmContractAddress,
key,
val,
'0x',
'0x'
)
).to.be.revertedWith(
'Storage slot was not changed or has already been committed.'
)
})
})
describe('when the slot was changed or not already committed', () => {
beforeEach(() => {
Mock__OVM_StateManager.smocked.commitContractStorage.will.return.with(
true
)
})
describe('with a valid proof', () => {
let accountTrieProof: string
let storageTrieProof: string
let postStateRoot: string
beforeEach(async () => {
const storageGenerator = await TrieTestGenerator.fromNodes({
nodes: [
{
key,
val,
},
],
secure: true,
})
const storageTest = await storageGenerator.makeNodeUpdateTest(
key,
newVal
)
const generator = await TrieTestGenerator.fromAccounts({
accounts: [
{
...account,
storageRoot: storageTest.root,
address: ovmContractAddress,
},
],
secure: true,
})
const test = await generator.makeAccountUpdateTest(
ovmContractAddress,
{
...account,
storageRoot: storageTest.newRoot,
}
)
Mock__OVM_StateManager.smocked.getAccount.will.return.with({
...account,
storageRoot: storageTest.root,
ethAddress: ZERO_ADDRESS,
isFresh: false,
})
describe('when the slot was changed', () => {
describe('when the slot has not been committed', () => {
it('should commit the slot and update the state', async () => {})
accountTrieProof = test.accountTrieWitness
storageTrieProof = storageTest.proof
postStateRoot = test.newAccountTrieRoot
OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot,
})
})
describe('when the slot was already committed', () => {
it('should revert', () => {})
it('should commit the slot and update the state', async () => {
await expect(
OVM_StateTransitioner.commitStorageSlot(
ovmContractAddress,
key,
newVal,
accountTrieProof,
storageTrieProof
)
).to.not.be.reverted
})
})
})
})
describe('completeTransition', () => {
beforeEach(() => {
OVM_StateTransitioner.smodify.set({
phase: 1,
})
})
describe('when there are uncommitted accounts', () => {
it('should revert', async () => {})
beforeEach(() => {
Mock__OVM_StateManager.smocked.getTotalUncommittedAccounts.will.return.with(
1
)
Mock__OVM_StateManager.smocked.getTotalUncommittedContractStorage.will.return.with(
0
)
})
it('should revert', async () => {
await expect(
OVM_StateTransitioner.completeTransition()
).to.be.revertedWith(
'All accounts must be committed before completing a transition.'
)
})
})
describe('when there are uncommitted storage slots', () => {
it('should revert', async () => {})
beforeEach(() => {
Mock__OVM_StateManager.smocked.getTotalUncommittedAccounts.will.return.with(
0
)
Mock__OVM_StateManager.smocked.getTotalUncommittedContractStorage.will.return.with(
1
)
})
describe('when all state changes are committed', () => {})
it('should revert', async () => {
await expect(
OVM_StateTransitioner.completeTransition()
).to.be.revertedWith(
'All storage must be committed before completing a transition.'
)
})
})
describe('when all state changes are committed', () => {
beforeEach(() => {
Mock__OVM_StateManager.smocked.getTotalUncommittedAccounts.will.return.with(
0
)
Mock__OVM_StateManager.smocked.getTotalUncommittedContractStorage.will.return.with(
0
)
})
it('should complete the transition', async () => {
await expect(OVM_StateTransitioner.completeTransition()).to.not.be
.reverted
expect(await OVM_StateTransitioner.isComplete()).to.equal(true)
})
})
})
})
/* tslint:disable:no-empty */
import { expect } from '../../../setup'
describe('Lib_OVMCodec', () => {
describe('decodeEOATransaction', () => {
describe('when given a valid RLP-encoded transaction', () => {
it('should return the decoded transaction struct', async () => {})
})
})
describe('encodeTransaction', () => {
it('should ABI encode (packed) the given transaction', async () => {})
})
describe('hashTransaction', () => {
it('should return the hash of the encoded transaction', async () => {})
})
})
/* External Imports */
import * as rlp from 'rlp'
/* Internal Imports */
import { Lib_RLPReader_TEST_JSON } from '../../../data'
import { runJsonTest, toHexString } from '../../../helpers'
describe('Lib_RLPReader', () => {
//console.log(JSON.stringify(Lib_RLPReader_TEST_JSON2, null, 4))
describe('JSON tests', () => {
runJsonTest('TestLib_RLPReader', Lib_RLPReader_TEST_JSON)
})
})
/* tslint:disable:no-empty */
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
/* Internal Imports */
import { Lib_RLPWriter_TEST_JSON } from '../../../data'
const encode = async (Lib_RLPWriter: Contract, input: any): Promise<void> => {
if (Array.isArray(input)) {
const elements = await Promise.all(
input.map(async (el) => {
return encode(Lib_RLPWriter, el)
})
)
return Lib_RLPWriter.writeList(elements)
} else if (Number.isInteger(input)) {
return Lib_RLPWriter.writeUint(input)
} else if (input[0] === '#') {
return Lib_RLPWriter.writeInt(input.slice(1))
} else {
return Lib_RLPWriter.writeString(input)
}
}
describe('Lib_RLPWriter', () => {
let Lib_RLPWriter: Contract
before(async () => {
Lib_RLPWriter = await (
await ethers.getContractFactory('TestLib_RLPWriter')
).deploy()
})
describe('Official Ethereum RLP Tests', () => {
for (const [key, test] of Object.entries(Lib_RLPWriter_TEST_JSON)) {
it(`should properly encode: ${key}`, async () => {
expect(await encode(Lib_RLPWriter, test.in)).to.equal(test.out)
})
}
})
})
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
/* Internal Imports */
import { TrieTestGenerator } from '../../../helpers'
const NODE_COUNTS = [1, 2, 128, 256, 512, 1024, 2048, 4096]
describe('Lib_MerkleTrie', () => {
let Lib_MerkleTrie: Contract
before(async () => {
Lib_MerkleTrie = await (
await ethers.getContractFactory('TestLib_MerkleTrie')
).deploy()
})
describe('verifyInclusionProof', () => {
for (const nodeCount of NODE_COUNTS) {
describe(`inside a trie with ${nodeCount} nodes`, () => {
let generator: TrieTestGenerator
before(async () => {
generator = await TrieTestGenerator.fromRandom({
seed: `seed.incluson.${nodeCount}`,
nodeCount,
secure: false,
})
})
for (
let i = 0;
i < nodeCount;
i += nodeCount / (nodeCount > 8 ? 8 : 1)
) {
it(`should correctly prove inclusion for node #${i}`, async () => {
const test = await generator.makeInclusionProofTest(i)
expect(
await Lib_MerkleTrie.verifyInclusionProof(
test.key,
test.val,
test.proof,
test.root
)
).to.equal(true)
})
}
})
}
})
describe('update', () => {
for (const nodeCount of NODE_COUNTS) {
describe(`inside a trie with ${nodeCount} nodes`, () => {
let generator: TrieTestGenerator
before(async () => {
generator = await TrieTestGenerator.fromRandom({
seed: `seed.update.${nodeCount}`,
nodeCount,
secure: false,
})
})
for (
let i = 0;
i < nodeCount;
i += nodeCount / (nodeCount > 8 ? 8 : 1)
) {
it(`should correctly update node #${i}`, async () => {
const test = await generator.makeNodeUpdateTest(
i,
'0x1234123412341234'
)
expect(
await Lib_MerkleTrie.update(
test.key,
test.val,
test.proof,
test.root
)
).to.equal(test.newRoot)
})
}
})
}
})
describe('get', () => {
for (const nodeCount of NODE_COUNTS) {
describe(`inside a trie with ${nodeCount} nodes`, () => {
let generator: TrieTestGenerator
before(async () => {
generator = await TrieTestGenerator.fromRandom({
seed: `seed.get.${nodeCount}`,
nodeCount,
secure: false,
})
})
for (
let i = 0;
i < nodeCount;
i += nodeCount / (nodeCount > 8 ? 8 : 1)
) {
it(`should correctly get the value of node #${i}`, async () => {
const test = await generator.makeInclusionProofTest(i)
expect(
await Lib_MerkleTrie.get(test.key, test.proof, test.root)
).to.deep.equal([true, test.val])
})
}
})
}
})
})
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
/* Internal Imports */
import { TrieTestGenerator } from '../../../helpers'
const NODE_COUNTS = [1, 2, 128, 256, 512, 1024, 2048, 4096]
describe('Lib_SecureMerkleTrie', () => {
let Lib_SecureMerkleTrie: Contract
before(async () => {
Lib_SecureMerkleTrie = await (
await ethers.getContractFactory('TestLib_SecureMerkleTrie')
).deploy()
})
describe('verifyInclusionProof', () => {
for (const nodeCount of NODE_COUNTS) {
describe(`inside a trie with ${nodeCount} nodes`, () => {
let generator: TrieTestGenerator
before(async () => {
generator = await TrieTestGenerator.fromRandom({
seed: `seed.incluson.${nodeCount}`,
nodeCount,
secure: true,
})
})
for (
let i = 0;
i < nodeCount;
i += nodeCount / (nodeCount > 8 ? 8 : 1)
) {
it(`should correctly prove inclusion for node #${i}`, async () => {
const test = await generator.makeInclusionProofTest(i)
expect(
await Lib_SecureMerkleTrie.verifyInclusionProof(
test.key,
test.val,
test.proof,
test.root
)
).to.equal(true)
})
}
})
}
})
describe('update', () => {
for (const nodeCount of NODE_COUNTS) {
describe(`inside a trie with ${nodeCount} nodes`, () => {
let generator: TrieTestGenerator
before(async () => {
generator = await TrieTestGenerator.fromRandom({
seed: `seed.update.${nodeCount}`,
nodeCount,
secure: true,
})
})
for (
let i = 0;
i < nodeCount;
i += nodeCount / (nodeCount > 8 ? 8 : 1)
) {
it(`should correctly update node #${i}`, async () => {
const test = await generator.makeNodeUpdateTest(
i,
'0x1234123412341234'
)
expect(
await Lib_SecureMerkleTrie.update(
test.key,
test.val,
test.proof,
test.root
)
).to.equal(test.newRoot)
})
}
})
}
})
describe('get', () => {
for (const nodeCount of NODE_COUNTS) {
describe(`inside a trie with ${nodeCount} nodes`, () => {
let generator: TrieTestGenerator
before(async () => {
generator = await TrieTestGenerator.fromRandom({
seed: `seed.get.${nodeCount}`,
nodeCount,
secure: true,
})
})
for (
let i = 0;
i < nodeCount;
i += nodeCount / (nodeCount > 8 ? 8 : 1)
) {
it(`should correctly get the value of node #${i}`, async () => {
const test = await generator.makeInclusionProofTest(i)
expect(
await Lib_SecureMerkleTrie.get(test.key, test.proof, test.root)
).to.deep.equal([true, test.val])
})
}
})
}
})
})
/* Internal Imports */
import { Lib_Bytes32Utils_TEST_JSON } from '../../../data'
import { runJsonTest } from '../../../helpers'
describe('Lib_Bytes32Utils', () => {
describe('JSON tests', () => {
runJsonTest('TestLib_Bytes32Utils', Lib_Bytes32Utils_TEST_JSON)
})
})
/* Internal Imports */
import { Lib_BytesUtils_TEST_JSON } from '../../../data'
import { runJsonTest } from '../../../helpers'
describe('Lib_BytesUtils', () => {
describe('JSON tests', () => {
runJsonTest('TestLib_BytesUtils', Lib_BytesUtils_TEST_JSON)
})
})
/* Internal Imports */
import { Lib_ECDSAUtils_TEST_JSON } from '../../../data'
import { runJsonTest } from '../../../helpers'
describe('Lib_ECDSAUtils', () => {
describe('JSON tests', () => {
runJsonTest('TestLib_ECDSAUtils', Lib_ECDSAUtils_TEST_JSON)
})
})
/* tslint:disable:no-empty */
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, Signer } from 'ethers'
/* Internal Imports */
import {
ZERO_ADDRESS,
makeHexString,
fromHexString,
getHexSlice,
} from '../../../helpers'
describe('Lib_EthUtils', () => {
let signer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
})
let Lib_EthUtils: Contract
before(async () => {
Lib_EthUtils = await (
await ethers.getContractFactory('TestLib_EthUtils')
).deploy()
})
describe('getCode(address,uint256,uint256)', () => {
describe('when the contract does not exist', () => {
const address = ZERO_ADDRESS
describe('when offset = 0', () => {
const offset = 0
it('should return length zero bytes', async () => {
const length = 100
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(makeHexString('00', length))
})
})
describe('when offset > 0', () => {
const offset = 50
it('should return length zero bytes', async () => {
const length = 100
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(makeHexString('00', length))
})
})
})
describe('when the account is an EOA', () => {
let address: string
before(async () => {
address = await signer.getAddress()
})
describe('when offset = 0', () => {
const offset = 0
it('should return length zero bytes', async () => {
const length = 100
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(makeHexString('00', length))
})
})
describe('when offset > 0', () => {
const offset = 50
it('should return length zero bytes', async () => {
const length = 100
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(makeHexString('00', length))
})
})
})
describe('when the contract exists', () => {
let address: string
let code: string
let codeLength: number
before(async () => {
address = Lib_EthUtils.address
code = await ethers.provider.getCode(address)
codeLength = fromHexString(code).length
})
describe('when offset = 0', () => {
const offset = 0
describe('when length = 0', () => {
const length = 0
it('should return empty', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal('0x')
})
})
describe('when 0 < length < extcodesize(contract)', () => {
let length: number
before(async () => {
length = Math.floor(codeLength / 2)
})
it('should return N bytes from the start of code', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(getHexSlice(code, offset, length))
})
})
describe('when length = extcodesize(contract)', () => {
let length: number
before(async () => {
length = codeLength
})
it('should return the full contract code', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(code)
})
})
describe('when length > extcodesize(contract)', () => {
let length: number
before(async () => {
length = codeLength * 2
})
it('should return the full contract code padded to length with zero bytes', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(code + '00'.repeat(codeLength))
})
})
})
describe('when 0 < offset < extcodesize(contract)', () => {
let offset: number
before(async () => {
offset = Math.floor(codeLength / 2)
})
describe('when length = 0', () => {
const length = 0
it('should return empty', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal('0x')
})
})
describe('when 0 < length < extcodesize(contract) - offset', () => {
let length: number
before(async () => {
length = Math.floor((codeLength - offset) / 2)
})
it('should return the selected bytes', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(getHexSlice(code, offset, length))
})
})
describe('when length = extcodesize(contract) - offset', () => {
let length: number
before(async () => {
length = codeLength - offset
})
it('should return the selected bytes', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(getHexSlice(code, offset, length))
})
})
describe('when length > extcodesize(contract) - offset', () => {
let length: number
let extraLength: number
before(async () => {
length = (codeLength - offset) * 2
extraLength = length - (codeLength - offset)
})
it('should return the selected bytes padded to length with zero bytes', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(
getHexSlice(code, offset, codeLength - offset) +
'00'.repeat(extraLength)
)
})
})
})
describe('offset >= extcodesize(contract)', () => {
let offset: number
before(async () => {
offset = codeLength * 2
})
describe('when length = 0', () => {
const length = 0
it('should return empty', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal('0x')
})
})
describe('when length > 0', () => {
let length: number
before(async () => {
length = codeLength * 2
})
it('should return length zero bytes', async () => {
expect(
await Lib_EthUtils['getCode(address,uint256,uint256)'](
address,
offset,
length
)
).to.equal(makeHexString('00', length))
})
})
})
})
})
describe('getCode(address)', () => {
describe('when the contract does not exist', () => {})
describe('when the account is an EOA', () => {})
describe('when the contract exists', () => {})
})
describe('getCodeSize', () => {
describe('when the contract does not exist', () => {})
describe('when the account is an EOA', () => {})
describe('when the contract exists', () => {})
})
describe('getCodeHash', () => {
describe('when the contract does not exist', () => {})
describe('when the account is an EOA', () => {})
describe('when the contract exists', () => {})
})
describe('createContract', () => {
describe('it should create the contract', () => {})
})
describe('getAddressForCREATE', () => {
describe('when the nonce is zero', () => {
describe('it should return the correct address', () => {})
})
describe('when the nonce is > 0', () => {
describe('it should return the correct address', () => {})
})
})
describe('getAddressForCREATE2', () => {
describe('when the bytecode is not empty', () => {
describe('when the salt is not zero', () => {
describe('it should return the correct address', () => {})
})
describe('when the salt is zero', () => {
describe('it should return the correct address', () => {})
})
})
describe('when the bytecode is empty', () => {
describe('when the salt is not zero', () => {
describe('it should return the correct address', () => {})
})
describe('when the salt is zero', () => {
describe('it should return the correct address', () => {})
})
})
})
})
/* 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))
})
})
})
export { tests as Lib_RLPWriter_TEST_JSON } from './json/libraries/rlp/Lib_RLPWriter.test.json'
export { tests as Lib_RLPReader_TEST_JSON } from './json/libraries/rlp/Lib_RLPReader.test.json'
export { tests as Lib_Bytes32Utils_TEST_JSON } from './json/libraries/utils/Lib_Bytes32Utils.test.json'
export { tests as Lib_BytesUtils_TEST_JSON } from './json/libraries/utils/Lib_BytesUtils.test.json'
export { tests as Lib_ECDSAUtils_TEST_JSON } from './json/libraries/utils/Lib_ECDSAUtils.test.json'
export { tests as Lib_MerkleTrie_TEST_JSON } from './json/libraries/trie/Lib_MerkleTrie.test.json'
export { tests as CREATE2_TEST_JSON } from './json/create2.test.json'
export { tests as SAFETY_CHECKER_TEST_JSON } from './json/safety-checker.test.json'
......@@ -2,7 +2,7 @@
"source": "https://eips.ethereum.org/EIPS/eip-1014",
"notes": "added additional tests with more bytecode",
"date": "2020-01-10",
"create2Tests": {
"tests": {
"all zero values": {
"address": "0x0000000000000000000000000000000000000000",
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
......
{
"tests": {
"readBool": {
"true": {
"in": [
"0x01"
],
"out": [
true
]
},
"false": {
"in": [
"0x00"
],
"out": [
false
]
}
},
"readAddress": {
"valid address": {
"in": [
"0x941212121212121212121212121212121212121212"
],
"out": [
"0x1212121212121212121212121212121212121212"
]
}
},
"readBytes": {
"bytestring00": {
"in": [
"0x00"
],
"out": [
"0x00"
]
},
"bytestring01": {
"in": [
"0x01"
],
"out": [
"0x01"
]
},
"bytestring7F": {
"in": [
"0x7f"
],
"out": [
"0x7f"
]
}
},
"readString": {
"emptystring": {
"in": [
"0x80"
],
"out": [
""
]
},
"shortstring": {
"in": [
"0x83646f67"
],
"out": [
"dog"
]
},
"shortstring2": {
"in": [
"0xb74c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c69"
],
"out": [
"Lorem ipsum dolor sit amet, consectetur adipisicing eli"
]
},
"longstring": {
"in": [
"0xb8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974"
],
"out": [
"Lorem ipsum dolor sit amet, consectetur adipisicing elit"
]
},
"longstring2": {
"in": [
"0xb904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174"
],
"out": [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat"
]
}
},
"readUint256": {
"zero": {
"in": [
"0x80"
],
"out": [
0
]
},
"smallint": {
"in": [
"0x01"
],
"out": [
1
]
},
"smallint2": {
"in": [
"0x10"
],
"out": [
16
]
},
"smallint3": {
"in": [
"0x4f"
],
"out": [
79
]
},
"smallint4": {
"in": [
"0x7f"
],
"out": [
127
]
},
"mediumint1": {
"in": [
"0x8180"
],
"out": [
128
]
},
"mediumint2": {
"in": [
"0x8203e8"
],
"out": [
1000
]
},
"mediumint3": {
"in": [
"0x830186a0"
],
"out": [
100000
]
}
},
"readList": {
"emptylist": {
"in": [
"0xc0"
],
"out": [
[]
]
},
"stringlist": {
"in": [
"0xcc83646f6783676f6483636174"
],
"out": [
[
"0x83646f67",
"0x83676f64",
"0x83636174"
]
]
},
"multilist": {
"in": [
"0xc6827a77c10401"
],
"out": [
[
"0x827a77",
"0xc104",
"0x01"
]
]
},
"shortListMax1": {
"in": [
"0xf784617364668471776572847a78637684617364668471776572847a78637684617364668471776572847a78637684617364668471776572"
],
"out": [
[
"0x8461736466",
"0x8471776572",
"0x847a786376",
"0x8461736466",
"0x8471776572",
"0x847a786376",
"0x8461736466",
"0x8471776572",
"0x847a786376",
"0x8461736466",
"0x8471776572"
]
]
},
"longList1": {
"in": [
"0xf840cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376"
],
"out": [
[
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376"
]
]
},
"longList2": {
"in": [
"0xf90200cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376"
],
"out": [
[
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376",
"0xcf84617364668471776572847a786376"
]
]
},
"listsoflists": {
"in": [
"0xc4c2c0c0c0"
],
"out": [
[
"0xc2c0c0",
"0xc0"
]
]
},
"listsoflists2": {
"in": [
"0xc7c0c1c0c3c0c1c0"
],
"out": [
[
"0xc0",
"0xc1c0",
"0xc3c0c1c0"
]
]
},
"dictTest1": {
"in": [
"0xecca846b6579318476616c31ca846b6579328476616c32ca846b6579338476616c33ca846b6579348476616c34"
],
"out": [
[
"0xca846b6579318476616c31",
"0xca846b6579328476616c32",
"0xca846b6579338476616c33",
"0xca846b6579348476616c34"
]
]
}
}
}
}
......@@ -3,7 +3,7 @@
"notes": "Removed BigInt test, since ethers does not support integers over 10^16: https://github.com/ethers-io/ethers.js/issues/418",
"latestcommit": "b2dcd19973637ac05e17646378dac9cbb2927075",
"date": "2020-01-08",
"rlpTests": {
"tests": {
"emptystring": {
"in": "",
"out": "0x80"
......
{
"tests": {
"update": {
"basic leaf value updates": {
"in": [
"0x6b6579316161",
"0x736f6d65206e65772076616c7565",
"0xf8a2a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080b0ef83206161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
"0xc6049f657b848e7a811a366d60dbd8fed5edb1432f493fcd11eb882d2fb38470"
]
},
"new leaf insertions": {
"in": [
"0x6b6579346464",
"0x736f6d65206e65772076616c7565",
"0xf871a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
"0x2d9bfc0b1e73cf420cb79ec039a28f4449de3fe875f455f34c94a867be300c7b"
]
},
"modifications to extension node": {
"in": [
"0x6b6579316162",
"0x736f6d65206e65772076616c7565",
"0xf8a2a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080b0ef83206161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
"0x0ef0b67d8f45c0e6a1673541196a121bc12639fae8026ac2594961faaa0dbac5"
]
},
"shift existing value to branch": {
"in": [
"0x6b657931616161",
"0x736f6d65206e65772076616c7565",
"0xf8a2a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080b0ef83206161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
"0x4efa4e217b51fa5c79112e45727f7754882db71320275403fb9f2c74686ac577"
]
},
"shift new value to branch": {
"in": [
"0x6b65793161",
"0x736f6d65206e65772076616c7565",
"0xf8a2a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080b0ef83206161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
"0x73fb38b2ad097f755eb8c3485035ee04960717956dd5ccd0ea086afe55538772"
]
},
"arbitrary update (128 nodes)": {
"in": [
"0x38306562633864376331633237656437393930613731633839656164323463636431343836336662626466336662356462316339313632636661633436663833",
"0x66623237633466393532353865363964303232623963373766616132643166356339643030336233616365306662353264373163613635393263326666303134",
"0xf9033db853f851808080a01683853733c3f60794d4c72f7b2f72afdb66c78c003d5447007e6570aabbce488080a075779c9fa7a787bd9e9b4793d8570ab8d00a6da215b1149d8adbffe15b72035c80808080808080808080b90154f90151a0ca8487ac08712727a9f015ea79a5425dff3e2827444594ad319899054bea6726a05bc9e58c52524b0e143297bf4641a131fefdf0457deaf17a06a4ee3d5b47e236a03c79e1171977ad0d6147cd8567dc5a427012007fcfb20e0f9e6c53e8d2783bdfa08b4e2951e45c5323519d148fc7224d2bceff6a1109102e877fcb6172b85270c4a0483a71a1dc202bc4793dda2491e0cf6caa80f4e8c41490faf0ccff36f36ab575a0cfc4cc320fdc887c1a6393d3dfa2e10693570fb9e09814b078a4e22788d7b1baa0db9c9f4674af3d18cc1249ac6712663d7d3241a9b7a358d3b7f287a7abbfed77a07e8f510894db4c610cb31efa5a3fa90776649c92edd4abbd550bc06a7c5f0598a0ad558331f2fff09e6c6c11a6e10f28e248729492349d1e44f7c790fc6630fe25a0a328bd791bddc75a9f835d68807b7dfcdaa232816ef0a8f056b104d566bc5b1b80808080808080b853f851808080a0d4bf75bea1883976cf54999f17b869595c833157bac7733ae45905f7894a63ca8080a0809edbfbc8b6e1bbe5bc8731af253337a57ecc4a2432182650f85a46a97e20f780808080808080808080b8b3f8b1a0922c98754d7ef4b203da9439946a6f7aba8339eb4f065cf83200724d36a9e86380a0437d659b7c49ce690feac212b95ebf84253c55aafc410c3b61321ee11a5d83358080a02eed03a131ad24ed59f25a4b433b0a39e6f3e5e95bc7a0b663c9b6edfae9e47aa0b07d0b40ba348e26858be73fed53d3a6e439c4d05a40e04c7fd53167c8f1d2948080a0f7d7d33621813da6bb91701839fc79fd6d261b2a907a43106d2bc978481238a080808080808080b885f883b83f206562633864376331633237656437393930613731633839656164323463636431343836336662626466336662356462316339313632636661633436663833b84066623237633466393532353865363964303232623963373766616132643166356339643030336233616365306662353264373163613635393263326666303134",
"0xce74ff675fd0b952ecb3f02f5360d50d1b96254f21d0930f2ced2f328553f972"
],
"out": [
"0xce74ff675fd0b952ecb3f02f5360d50d1b96254f21d0930f2ced2f328553f972"
]
},
"arbitrary update (256 nodes)": {
"in": [
"0x30653837306437643232636366313635333231323139333737396537316666376634373930303139626533613839633037616138343262653134643465363937",
"0x39616533313162356138663533333733353135323439383231386663613165323237313763343764326463366135663738323539316433663462326434326134",
"0xf903d5b853f851808080a0939d6c424a8898660e415e2427e923f6f825296f7d2d03a7e870f4de13bdc8248080a09d9ea16458b048e853ee27c96b095519712020e617aaf03de69c71a19850287880808080808080808080b90154f90151a060e0aead56eb92bea0b9233e611a141493ee03864a5b46b54ead8cf4f3fafb41a0275c4007dd73d30b8290c99035cd2d72e6d9a652853253b8176be4eaa6ce03a9a0ed1f9432a10f62ebdf2748baecdbae01df7e4b72b793692f5b46ccb1b41c081ca01f2a5f3f5c4b597bf354e84a8ce80d9a4ffd274045a06ac636e1a394b877aed0a0bd04555f2a1b7d5dbe5b0fd869b9d2d37374f2d23e4ba4e4e695c173614fcf51a0f238e46cad89131c919d406183ef03ed46231a257231bc89bb640af0868768bca0973f643ff33b27848bf26f2d0d3988c05105715c00c2db106fce906983e62ee9a001bdf81f3c1c1bac6428259d6d4e0edbdd8ceb7c1d14866125a0036def676d40a0d8926874fd8a0e0ab8d46c4407ead6c055444c548e8cc4feca97d4a9bfc65f0ca058fea35b638a9b061e597f1ab17820a04c67436c64c575e9d4d3a50c4209ad5980808080808080b853f851808080a00d8de33b14b40038395424871f1daaeed399dc5ea7873b16fb56fab0beb68c9e8080a0443a68a5b803a8a34906d8664c759a4b00f866ff2d78af2298b110b96884ce2580808080808080808080b8b3f8b18080a018c1c3076c61714d6dc546ac283e35b5876708f8162559b5054e902975436a41a018085b2e94545aae48c1ec8e972ca02c8f6cded9d2f6033c1b0209ad20ff4be2a02cbfcfa4580c95efc154891b19b6458f073126beeeee7e1fb0c232e9bdb7839ea0a862ecc9386c6d0fcad6fedf47ad8c15ee45a88e8a031a6320f7bb22b6bd3ab7a08687183169c39a78988413c75facc0424a563312425ad5249c1bd865de459d4880808080808080808080a3e213a0cb71f0cc92c51e0d8eb9dd47e461f2077bfbe01f2125f181a69ca866f131f0d4b873f871a0d6883f50188ca6cee1d5bce7f2deaa67b04a9c82606f88bc7b597866ecdd41878080808080a0bb16a4c9def487361c336de32752a110a711dda21c1155663772db84392b085e80a0d7a33b3192a85c741c3494463098dfaf7f342ea1def97b44be03091a4e1cf9b68080808080808080b884f882b83e2037306437643232636366313635333231323139333737396537316666376634373930303139626533613839633037616138343262653134643465363937b84039616533313162356138663533333733353135323439383231386663613165323237313763343764326463366135663738323539316433663462326434326134",
"0x1d1c6d6618a09e6d00460ecbb34e07344c1428ada1b33d440e336ae18da193e1"
],
"out": [
"0x1d1c6d6618a09e6d00460ecbb34e07344c1428ada1b33d440e336ae18da193e1"
]
},
"arbitrary update (512 nodes)": {
"in": [
"0x62663635326166303465303333633037373634623665646536643637643936393733393364386438373661656433363833373437393933363536303732366134",
"0x34333837346462663832303033646432306561393631613730666336343163653035303761613539393830376262353565396138383631396231373133333065",
"0xf9032fb853f851808080a0172aa06a9c3bf30cc3fa2521909faa0f755519d56eacdf0dadae12ab4b9eaef88080a0ca59486747b7dd466a0059a2ba4ceabd531192e18c460ac60132b56cfc8a48c080808080808080808080b8d3f8d180a05df972283599f8ffdcb8f9d003bd1aa859942764b1e5db1613d914e16ebd122ea0d57d611996f626f1da73a876f1db74d44a1467d20357247c31c8619a903674fca046bb69112b28082bc8fae40d752b083d7ec3103aa1abf0ab036c961db41ef0f3a0ebeac2aabd3939a84e8a8f4647b27f02d4f6b6df962f5b350282bfa9d291773aa083d6cca4848f341a8003764420b2ad3605425cc23d12afcb65933ab606f339cca03c23d875a3225d63d85ef83fc999121284f31889d8201ac0be9cb1a8d5cd073d80808080808080808080b853f851808080a02a991efab0bb245d1d8908fdb29c9aef8243378f9fb22ea5861b35e355ddc2d18080a0a66cfb941cb9f47af573f8738cd2a56a625392de974e5930e24594e9f258714980808080808080808080b8d3f8d180a0a1e1cf55385e1b6da7cc2fb7aeb59c766f154223fd8821a1914ecbc5927f72f8a0b947c9b788bd2f0ae8f3ffa4078721d266ed88ddda976b66199d9a7d1aa2068fa0d4306d3c5bee7448422edc43f806365de24f17a6c6a0e17e1b3260454a23b9f8a0cb1420601892d3ae953e17cd628428766ec281cc442720df72b6b03a648bea2ba07885db0751e3008e64b587a095c99fd6673f189a23cdd818b7d89943aace6984a0b64746e1e804b6c746ff52316a8428ee3ddcb9e6528b5fc7d5e13e71b22069e680808080808080808080b853f851808080a0d12e64290951bd56fa8626a63e05bcf239a0225d602dd9b9a7e478edb77228428080a00471402a244af307ccb5ee16d236b0278d8c668eb80b7c438bbe35e62c1a5abc80808080808080808080b884f882b83e3635326166303465303333633037373634623665646536643637643936393733393364386438373661656433363833373437393933363536303732366134b84034333837346462663832303033646432306561393631613730666336343163653035303761613539393830376262353565396138383631396231373133333065",
"0x922b383ea8414274391a09d26e26862bcb4018838878177f909e7578486d4101"
],
"out": [
"0x922b383ea8414274391a09d26e26862bcb4018838878177f909e7578486d4101"
]
},
"arbitrary update (1024 nodes)": {
"in": [
"0x63633939363830316465653630663865353732336635303766613037646236653930643737373566366665623564653232346136313762393533333561663861",
"0x34386665633731636135326230306266316233333262623263303961646137353736333635656165623462343663336431326665346134333763336132636339",
"0xf903d8b853f851808080a07dbe425836ecb64269b04bd8bdaaf162e7dc011bc545ea3c114924791b9715f38080a05daae9e121770c2ce2c8aaae5aef8294266cb421e7394d833973b30d292476b880808080808080808080b8d3f8d180a0321c3ed3b4591371add24ae89ac39364c90085e572082704d679d937b8480b0ea08ee53c6695830154881b03611e3b479c095e010c54e4d2f0d98803242beef8eaa07da013493bb886430b15d341bf459fd8af42367cf7e4f86d9f6a3287704008d0a0b2b02fa967f822b5cb0a99274be4064933cb1b51489298767acd3b1d2d17d869a000b574b33dce2ff6e2834990bfffaf726fff5e66a639510bf85db62b9f72a757a00325ca019c291cf1d02b0b55808a12e248595a94333fe0c20a5b0917964fdf9080808080808080808080b853f851808080a0aec555ba5424e2039fd0fab02bc307a75d3295a79c1f90c05bd9259781b2c1478080a09de84124a9512c6d0dcecc7693c1c71e7dae4e87ba93cdef66120a0f34e2165880808080808080808080b8d3f8d180a0fd5d7c497457334ac50c181c07640067cb02623a774cf3043c6fe6444f54425fa0c775c400a320fbedcce51129dabfd3b76495a644c7d0e83a59c14610c1e47ef8a01f30ff4af3bf01e544840ced373297840864672ec1bea4fe3125878ecf7e97d9a09015fce5cfd1a7271c3d3768be994cccbc8d750154c8bc2e0ce0c3ff4ef9386fa00f087471655a490fe86f60c824f47b25fc607dbfe83a4f987f028ac4eb03a3c1a0b72b82b32748b14079f21520ef9a27290ce7064411664f1ae88f5d9e6d45494480808080808080808080b853f851808080a0657b5ebb555e7d04261deefcde723bcd744c85ac1f887acbec03a315215ab5aa8080a0f244b0b57ca617ab32e2b190a9499040e6a98e2e58ee481d89ec9eaa110c5a1b80808080808080808080b853f85180a01275e04aeba9c0367572783ccb1561695d264293a3bc337fb712bbd58efa14f180808080808080a0c709e24f4afc4d46c3412e18ccd20cd1bf1da620129c42da1938b05c7276d19880808080808080b853f851808080a044f42acab8ddedef537f8b1f56c38a38d4667d3a341503eacbdc46386eb215d48080a0c19dc41cabe37a3da398a2d46ca409803cf77db8b7bcbd6acc90f00f8ee9e0ab80808080808080808080b883f881b83d39363830316465653630663865353732336635303766613037646236653930643737373566366665623564653232346136313762393533333561663861b84034386665633731636135326230306266316233333262623263303961646137353736333635656165623462343663336431326665346134333763336132636339",
"0x7cc1d9e4349a14570903256d0662d5c7e26259c23fce53d4e0d1d05b79144651"
],
"out": [
"0x7cc1d9e4349a14570903256d0662d5c7e26259c23fce53d4e0d1d05b79144651"
]
},
"arbitrary update (2048 nodes)": {
"in": [
"0x63616265366136313331376466623239383931636136363563353936643366386534323935336665356530323365356332396163633134653833393030303033",
"0x63303132633333643333393163363537396131383232346564316331356531613935353833333234303032616137383065333632356533393139306238643265",
"0xf903a4b853f851808080a06b1ec163d36f0660cab4ab85a101a2dd24bfa2658e1b722a3a0e3f40ad06a2f28080a05a944280b38ede1983f3dab5166cc620b846e9d3e8425f7d81ce610dd496063380808080808080808080b8d3f8d180a0bfc332079e121621b6a79d574595cca74cef134939aea106bd56202519ebf5baa0b9866e81a81052c8b5f57949466726518aa49ece30ea07fdd5d2834fcfe9676fa0544fed534ae1e285bf78468daf73b18e06fe3af92e30686ddaeae6e2d09642bca0d5227397e6129b675f39f09e9efbc654bf4ff566111be6b1c189c85f9684e86ca0f2ed72bcc8939e9671b1802de95264bccfb9677feed79c9c389191aa2fc1dc72a0d1384e17ccdc5b370fdc94c137a95569b5cd46847499c755f2d269b2dbbc20bc80808080808080808080b853f851808080a0d6d22f3b60400e4091958d2a9e0481c28a5591cc84877c1128ed37bd005fbc8f8080a05b56153cdf09797a3b4c87cadb4d08ba274d864abbda957a2d6320d6ed17dac880808080808080808080b8d3f8d180a042746ed1a4d5569b703154c91a201a1691f76794777cfe693ce357b529f47e16a09a7077c59c45ff2d95732ce9198404c9bae5c996e44271ef7f40b42b2aa7337ca0a5c16713226e147008cae877e7561e3fdd05872e96c729f6cd95f35b272be1cea0790fd15585bfc861cea60558e356f1a80f6da67e21d9b0b89402262d1bfcc585a05a03571e90510329c97a3b2a7720d14851a21b8972e97963c053af4e4e0d1a9ea08d0005d1533fb093fdbad029b4528e50aace6721d9810689d085586770242f5380808080808080808080b853f851808080a07b40b3c18f9cadd46aedac7661cc01579ec5cfe9e60b568cc79ccd84e4b81c7e8080a009e603f66a4b102b5b3c2525fc158ecb695781faba4ac01c5ab20c0b1686722080808080808080808080b873f8718080a0de8f63df18f1f49de180b12a0ced021aa75bc705395bf23f2ca2357c24c6e98580a06ee2f7499d6e5c9e5ee5529d4d40efd76ba7371648816da6c2cedccbfc014e6380a01f54a437431e714f7f9c3c0f79def239851df7d89f9e4520e3dc02bdd349be8180808080808080808080b884f882b83e2065366136313331376466623239383931636136363563353936643366386534323935336665356530323365356332396163633134653833393030303033b84063303132633333643333393163363537396131383232346564316331356531613935353833333234303032616137383065333632356533393139306238643265",
"0x091a8393aaece43e0f6efb3977f15fc6f86709cdd6f8c2ba9245ed2ba841a329"
],
"out": [
"0x091a8393aaece43e0f6efb3977f15fc6f86709cdd6f8c2ba9245ed2ba841a329"
]
}
},
"verifyInclusionProof": {
"basic inclusion proof (node 1 of 3)": {
"in": [
"0x6b6579316161",
"0x303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xf8a2a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080b0ef83206161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
true
]
},
"basic inclusion proof (node 2 of 3)": {
"in": [
"0x6b6579326262",
"0x6176616c32",
"0xf87da7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c33808080808080808080808080808bca83206262856176616c32",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
true
]
},
"basic inclusion proof (node 3 of 3)": {
"in": [
"0x6b6579336363",
"0x6176616c33",
"0xf87da7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c33808080808080808080808080808bca83206363856176616c33",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
true
]
},
"single long key": {
"in": [
"0x6b6579316161",
"0x303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xf5b4f387206b6579316161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xf838216fa749aefa91e0b672a9c06d3e6e983f913d7107b5dab4af60b5f5abed"
],
"out": [
true
]
},
"single short key": {
"in": [
"0x6b6579316161",
"0x3031323334",
"0xd08fce87206b6579316161853031323334",
"0x37956bab6bba472308146808d5311ac19cb4a7daae5df7efcc0f32badc97f55e"
],
"out": [
true
]
},
"key in the middle (node 2 of 6)": {
"in": [
"0x6b657931",
"0x30313233343536373839303132333435363738393031323334353637383930313233343536373839566572795f4c6f6e67",
"0xf90103a7e68416b65793a0f3f387240403976788281c0a6ee5b3fc08360d276039d635bb824ea7e6fed779b873f87180a034d14ccc7685aa2beb64f78b11ee2a335eae82047ef97c79b7dda7f0732b9f4ca05fb052b64e23d177131d9f32e9c5b942209eb7229e9a07c99a5d93245f53af18a09a137197a43a880648d5887cce656a5e6bbbe5e44ecb4f264395ccaddbe1acca80808080808080808080808080b864f862808080808080a057895fdbd71e2c67c2f9274a56811ff5cf458720a7fa713a135e3890f8cafcf8808080808080808080b130313233343536373839303132333435363738393031323334353637383930313233343536373839566572795f4c6f6e67",
"0xcb65032e2f76c48b82b5c24b3db8f670ce73982869d38cd39a624f23d62a9e89"
],
"out": [
true
]
},
"key in the middle (node 3 of 6)": {
"in": [
"0x6b6579326262",
"0x6176616c33",
"0xf8c9a7e68416b65793a0f3f387240403976788281c0a6ee5b3fc08360d276039d635bb824ea7e6fed779b873f87180a034d14ccc7685aa2beb64f78b11ee2a335eae82047ef97c79b7dda7f0732b9f4ca05fb052b64e23d177131d9f32e9c5b942209eb7229e9a07c99a5d93245f53af18a09a137197a43a880648d5887cce656a5e6bbbe5e44ecb4f264395ccaddbe1acca80808080808080808080808080a0df808080808080c9823262856176616c338080808080808080808573686f72748ac9823262856176616c33",
"0xcb65032e2f76c48b82b5c24b3db8f670ce73982869d38cd39a624f23d62a9e89"
],
"out": [
true
]
},
"key in the middle (node 4 of 6)": {
"in": [
"0x6b657932",
"0x73686f7274",
"0xf8bea7e68416b65793a0f3f387240403976788281c0a6ee5b3fc08360d276039d635bb824ea7e6fed779b873f87180a034d14ccc7685aa2beb64f78b11ee2a335eae82047ef97c79b7dda7f0732b9f4ca05fb052b64e23d177131d9f32e9c5b942209eb7229e9a07c99a5d93245f53af18a09a137197a43a880648d5887cce656a5e6bbbe5e44ecb4f264395ccaddbe1acca80808080808080808080808080a0df808080808080c9823262856176616c338080808080808080808573686f7274",
"0xcb65032e2f76c48b82b5c24b3db8f670ce73982869d38cd39a624f23d62a9e89"
],
"out": [
true
]
},
"key in the middle (node 5 of 6)": {
"in": [
"0x6b6579336363",
"0x6176616c33",
"0xf8e5a7e68416b65793a0f3f387240403976788281c0a6ee5b3fc08360d276039d635bb824ea7e6fed779b873f87180a034d14ccc7685aa2beb64f78b11ee2a335eae82047ef97c79b7dda7f0732b9f4ca05fb052b64e23d177131d9f32e9c5b942209eb7229e9a07c99a5d93245f53af18a09a137197a43a880648d5887cce656a5e6bbbe5e44ecb4f264395ccaddbe1acca80808080808080808080808080b83bf839808080808080c9823363856176616c338080808080808080809f313233343536373839303132333435363738393031323334353637383930318ac9823363856176616c33",
"0xcb65032e2f76c48b82b5c24b3db8f670ce73982869d38cd39a624f23d62a9e89"
],
"out": [
true
]
},
"embedded extension node (node 1 of 3)": {
"in": [
"0x61",
"0x61",
"0xf8389ad916d780c22061c22062c220638080808080808080808080808098d780c22061c22062c220638080808080808080808080808083c22061",
"0x72e6c01ad0c9a7b517d4bc68a5b323287fe80f0e68f5415b4b95ecbc8ad83978"
],
"out": [
true
]
},
"embedded extension node (node 2 of 3)": {
"in": [
"0x62",
"0x62",
"0xf8389ad916d780c22061c22062c220638080808080808080808080808098d780c22061c22062c220638080808080808080808080808083c22062",
"0x72e6c01ad0c9a7b517d4bc68a5b323287fe80f0e68f5415b4b95ecbc8ad83978"
],
"out": [
true
]
},
"embedded extension node (node 3 of 3)": {
"in": [
"0x63",
"0x63",
"0xf8389ad916d780c22061c22062c220638080808080808080808080808098d780c22061c22062c220638080808080808080808080808083c22063",
"0x72e6c01ad0c9a7b517d4bc68a5b323287fe80f0e68f5415b4b95ecbc8ad83978"
],
"out": [
true
]
},
"arbitrary proof (128 nodes)": {
"in": [
"0x62303236303063643530623533393762316233646561653437336538353661346435383763623634643436393830656434333061343832623832666635343139",
"0x64326331653939666237663661333132666364393263393738366164313935383236333038323066353934616435626566376363643133343463366363663866",
"0xf9029bb853f851808080a0263d78c5fd9968de40bbf3c9e54e46473ef873a6904ddea719e7c3d3dab8add38080a065ee105383f2f9afd87c32dff662715552f56e99619a6743e09386cf62f17fa680808080808080808080b8d3f8d180a0451fb51c00ca685f7a3b19c044491cd8e574f5a971b379249f3cb7a42aa2a13aa079c1b33a8132befd9ee33339a2040bffa955f2d024f81e9c8f8c10401ccbe294a09d80ad4228d7197dea4a8b18f7a99d34f28cb0ac1a5836a7cb93ffbaaf941464a08a56e14745b9622dbc8e80a33e1218e44b16860fd58f951610b963ee32462990a03f7186d3342a4a4084d1e8c22a40675a9df1dc130bd7a31ce2f89fc659de374ba065cbb9b66782fa5c519b78c5b2261ad005c47075b4ee8c08bc71255a4944027680808080808080808080b853f851808080a0e70153be669d9e0419001376857985895f1485f277d814fa41b6171038cae59c8080a01f76b2175e50963b4c2f5f8897eed7a3829cd0727d198138cf723b72ca3468f780808080808080808080b893f891a04ffa3d1d0c9eef905cf8cb924fe74c43715aa85c26c116c91a45e3599ef21e75a0c9b0859699e3d7f8367b4ca50f2b08acf1eea13d1dd0bb9b4155c4f67f4a5eea808080a0abe45f156c2bf6c743021a1aa9701ca27d384413bb25207685e3702791946a5f80a0042d75b1c061f41dcb5589780712929a0dff5ba1574a00fc6dad0ad3b99aa307808080808080808080b885f883b83f203236303063643530623533393762316233646561653437336538353661346435383763623634643436393830656434333061343832623832666635343139b84064326331653939666237663661333132666364393263393738366164313935383236333038323066353934616435626566376363643133343463366363663866",
"0xf450a6e84cd1e88c4736f19e62a46441d7e278d2be7e8031b892c20fdd58d8f0"
],
"out": [
true
]
},
"arbitrary proof (256 nodes)": {
"in": [
"0x64323930653331633662626339626130366139636465396635613463613434653737376133636237646165633937616432356661366264306536376431656539",
"0x36376538393266633931613736666130353365653138653439346565626339333231643936346438643166353630373436336565623334636435346665653938",
"0xf90373b853f851808080a0e82a9307013181b63bc631d9a72d8614ef53e14f57e7057ea40248fd0365716b8080a090721eb78858d84cfac57acc5fbe94ae8dba37b4d07c3e32e41929e20d793c6b80808080808080808080b8d3f8d180a0e8c88de9ecbbf4e12cc6aea87816733a25b50897cce68b04586dda7f69de71cca057b16f8b274f592ce9b76dd99444c506c554ef94e89abfc3f494c2c107402825a0e872edcec2088c1b0e28df3c238dee6326f36f2e6433dd6ba19cd1bc87631e5da041aaf5bd0b0f34049bbdbf1075ed79172bfaa5fc01002172fe58fbe7e2ccc6efa0a3167dd56c5070e353a8916d579f925932014ea982310e518d1916135671cc1ca0bf01933a5eb008945c734b8bd48b63177e9ad957bca3d676ed5eebfdb07367fe80808080808080808080b853f851808080a06f261637fdf4fd2ab7c63f93277ca37890e77c78ae8b132be882dfaf9b6293478080a0ab64547cb8ac7ee4550faaa792bdc6a906765b13c05edca5278ec6f34334f14180808080808080808080b8f3f8f18080a065f9749a6a0b992f5a4353e371d9c63d91648992b1eb6ae01ad8f62c965427a6a09512ebc2ead7a32b012bd5c72415541e4f90ffdf9549a96d884f47c9f2383937a06d6aa51d8d27a8cdc37115bfcbad5b489b95765d153594d77b25c41f7541223aa002554946d309fd21844d842c39e46f093e785403a46afb6957ab397b86c18f09a0a2d639fef7fb5dd52c7a2f3eb0703a0feb0558c524cc143d15ff9825c2c0b484a084f5a69d626b19ef0ae2705491bc237e4745d0d4cdd33a7dc3d1382a6eac0a6c80a03fdb322001905e71930066f5eafa499c9a5015db3a05be5d18ae8c2f46871dc680808080808080a3e213a025e631c84989b7d4360ca2eacc18f8e5ce6ecebb6d46ec6ed90e6c1215a0d53eb853f851808080a01cf50a2d534c03dd42e42986faecd8eb25f414d8e95c43bccd93ab0156f1948b8080808080a0b937b43f8140aa909ee07e2926df642ee3fd037391251943e68d7f3e8a45b10080808080808080b884f882b83e2030653331633662626339626130366139636465396635613463613434653737376133636237646165633937616432356661366264306536376431656539b84036376538393266633931613736666130353365653138653439346565626339333231643936346438643166353630373436336565623334636435346665653938",
"0xd4c61c8eac944a1efd2add3ae58fae739e125fb20eedd6982725ae28e2dd8832"
],
"out": [
true
]
},
"arbitrary proof (512 nodes)": {
"in": [
"0x30643137666135653037613430366166303734353434616163373133393434636366316138333863336261616530653236643533373837333566383931303664",
"0x30666464376438333431346665323264383534636665306232353031623065613864633337626639643738323765366265373631376333616138666132656362",
"0xf90406b853f851808080a0986c43ec04f20a5f840376e88792825cc36ed22d667afc8a4de8ca51a800ff9e8080a09e30593085f4b2f9105fcbab1535fc3830f002f5cce6cfdb28bb47242a5eb0d480808080808080808080b90154f90151a0c3377f117808ffda9540b34bc2462fd550460a184702a0d2b9bb1c08cb1911b2a0fe5312e04360355ebe6ea01af3c7be6330d9bd830921fbb1c00bc9eee79d01f1a07426a671125a91fe569577350bda466a176b86974a2da7fd5a5b3727cb193adaa0b941e322b523b68ae87d92da26e10118db53eb21e73b22d13a27535950bc327aa0244d5539892b472b1b0e42307b2bdf37deb8f884d5e409e562c877d359e5508fa0386c39ea3a957fd00ec258d8544fa27ed0efcb0710b874ba426373327fcf862aa00eb8c7848c95e74162d0418fa2efe365d1403326d52213790f9986b03685021da0fbabd66454942ba322cf8c3f3188cbe6cfc49d7de84068e3badf309bbe315e87a04a6ca2da03c5336b71f0281e4d4e0028a0a9b5cd10abc1fe966f11f6e260af75a0e6461917b4d216eeb2a47dc9ceae5f0d6bc5ee2778749b4edfad0c20e2be1ca480808080808080b853f851808080a0074b266998de98a026f62521d89dbda085bf10e7e1a461fc68f8740800166d738080a010e6611a79918c6e72cb8febcef11148b099a3bf12d8b849176d891d5d1b27da80808080808080808080b8d3f8d180a00e77c34d01560ddc1f0bec7821ce90d52f7d1155678fc0e4ce8be101abe94950a0e1bcc7aadf8bfa98a8d62997aee2eede55b84e056c56e85cc8e34658c79f9845a04ea9ad8b927af52ea15728141ce4209af93e521cf85a5c30c0d3578bf50bdda5a068c96643aa80297b7ea04ba70b40faeb39aa9b600f066c6c21c4c616aeb7f579a0813ed83afaaafabe055a27a2ca52826e9afcd95a700d55c24472a72486b275cca0b3c8c7ddf792c658c18b6da0c43f2381e35c995544402127de457ad9a69c4cc980808080808080808080b853f851808080a0e3bbd8835493be2266c3a4f4603a3f8318a4295e8f80d3b92b58b854f9f217998080a04b84bac7ffc30073a283ab3fd8d1035225fe2a670f3bc8da6633da2107a6ea1980808080808080808080b853f85180a04864ba68ed6e2baf1614729de3c40162608039dd1a001f4e64f248255a747bce8080a09d6fefed58a095129b74c1e79c16178d14b67fc1f6f82e9f43d9dcda0a9c8b2c808080808080808080808080b884f882b83e2037666135653037613430366166303734353434616163373133393434636366316138333863336261616530653236643533373837333566383931303664b84030666464376438333431346665323264383534636665306232353031623065613864633337626639643738323765366265373631376333616138666132656362",
"0x875642b42ff718ca2298d5c24e3129e470275f7f1bea5e756a620aa6f84d7172"
],
"out": [
true
]
},
"arbitrary proof (1024 nodes)": {
"in": [
"0x30613131623633616333643537616662626264643239623066313930663635303161353939646639646338383865326566633662623734326166393731353035",
"0x38303830666232376435303134393465376330666466346633323437396139656638326564383332616337383036646266303665616338353965376230666133",
"0xf90426b853f851808080a0d4e608f94f154c1086f14a3d0888aed6b264db44e295aeafcffec7af109cb3378080a0f35bba5f59720501bc9322f7489dd67e6e7d3182f3a0803d58881ea678c51a4580808080808080808080b90154f90151a0aca67bc56654411de846fb02d7fe66c3834d03519b0f58ecb86ccccc17dc1399a0a9856669cad391a03c193ec6dec2bc15bbcea1acff562aa322181d34bc15eae4a03ec4276daba3e20c341125fd1c239a3a59c68eaa8e662e04da9ee64555889b1ca0e53bd7370aab8439016df7bac1123e86aaab5f7d5fd7f70af271ba448716f627a0c3c137d47a74805600496927e626ea4e79e2abeb2cb7f24a746d9662de860693a09cd8e70d490b419266d209ca8830451e80a35378a57de1a3261c29f36a8ad480a02eed65535212cbad5b39bc59f4cb452839b94e15d852437c9b5075ab34ed78cba07137faf42fdeca8e0e6cad69be7a5d3b29dad2b5f881eae0261106f5e2afb017a0b0900dd08af215ddf59cb4aeff3367d4444bcfec999dc1241721500347dbb7dba03c57e508e13cd3ffec967ede493c43d0563eb2c67c4667bff443ae2546b75b5b80808080808080b853f851808080a051e8a2f385000faf1d92de1b23944ebd993dbed3b6b2db2c20a489a953c3f0828080a0760a75a384b342463c2c480a7875f967b8e2d664449b929cf2e4b1b884adb46e80808080808080808080b8d3f8d180a0ecb828ea580e16d237d4434fa01cd0712d6fda158bd8dc12a44d43736093ca80a02c3e341ba7cbb05876636d05ab2e0374f59b92fe21dfe74309ce1fe56edf692fa054a1b2ea1e057917e392a0c2c429e6b031c3f4a3cd1d9c36f2c45875ab6dfd25a007b648f34e53427c6770f1c556945d34f050f07a14f900fb852e7c514b45b068a0d4687eaa067aa1867147553a6dfc8e9d18c4afac87d44da258ecb35e5f96d89ba0d94e3a9ca51e70cda51dc56181afeb455774020367b71bc2d443c68cf563d6ce80808080808080808080b853f851808080a0185913e6e8315626922bbda00b2bc25841199d74af4083112ec596c446a3f1228080a035640fa9723db26d80b30ddcfca810e4782bb9f15a623d22050c443483ee653180808080808080808080b873f871a04f84c13bcdec814ba4ca136f37f40cb23ec1243577d2c0a06cc09bc8451e58e2a01544eae89eca15f429e589675458d781bcb94ac146fd84b3753acd5f255023c580a0bfcff9408e67a35a633ab460d44cccae0588ff8fccd0ae52311d55bcfa9ef57280808080808080808080808080b884f882b83e2031623633616333643537616662626264643239623066313930663635303161353939646639646338383865326566633662623734326166393731353035b84038303830666232376435303134393465376330666466346633323437396139656638326564383332616337383036646266303665616338353965376230666133",
"0xbe40ba4e41f77aa0c9760c2632971ea92b6c2ddcab3ba6939ab3f644aba1d0c5"
],
"out": [
true
]
},
"arbitrary proof (2048 nodes)": {
"in": [
"0x39633962623638323335393839323064623565323666306633636437626665343137366232343163383963333565373061323134373033643136356361616466",
"0x64393730343035346366613034306461346630373835313765316164363863336535316130336334396565373664396532393863396664656665663238333634",
"0xf90446b853f851808080a047cb3fcbe8077e8b5ccb4bd11d4e0385d26d4787259fbecd00406f4e4ceaac3a8080a04e1ee8279d78768445fdca4279172e63bdc4fa45ffe4793833e742dd532f7b3280808080808080808080b90154f90151a085f4a041e77f796039ac0e9b9ec972c7844dceb86b78e0d4d375d23497f67571a07fc1b9fba3de725d8419c6cfdc1c18c881800b320a4f197d944f8aefdcacb839a047b8c3b56ce6ab98642a461be1e87a09670a0e5b10766c11628e4aed02e58d96a0fa9f4f1db6f160f63ad02561c84c2926c0f91e28d592b7e5f25ab10e19247b00a0efd94cabb8fe54c224676c4a2d3fdaef7f797730aff42f05332b9897ad42b7fba0133ecca6da5528773f28e5640bd73c93215158193e5fcd5f57b40bd14cbee2b2a01d3919603586ebd347a0fbd6309adf42915432af91b6e1db6c49d25670754d6fa087b9cbe0cd61776540a84140464b7ba454009f2c18c828923145c419df478364a03a8b63739f9b25d5ec9182054c2511101152c5f7a0f4691ff7a014271c576fe9a082e004fb330f1e80956d1ef078f20efbec693528966d39f5a43ed12db482ef2c80808080808080b853f851808080a013d63cdd329388e9ad63e9e463355ef3f7bcc934aab10df83d0a92c6c0df99248080a0b9200cf83d6c887765012fa60ef295ad3394e34767e1f36dce1e951f08db820880808080808080808080b8d3f8d180a0047ba91080246e4bb0d7b8d6293449da0c4d04547627ffba0c73d9b36c0fc3faa0544c5e09b3c8e026b6752c057796e60978551d6ce77ccb6d26b71541cbf0ca13a02acf313da117de9e79c9fa88722d3146b240fafee0e4b37e8d1d2b4335d3c69da03f3ddc358c86b9b6ee2ba6eaa060ff3ed101626d26fcf25b9aafc867ea3b7bf5a0b65f0b79df2b27069da6070228c218026d501bee60b69b77c370b7450f027c87a0f659a7dea2ce9afe7753880268fbc913caf6d3fc4026d80934761046dcc2920b80808080808080808080b853f851808080a01c635a046d3838114189b399619b8f7d3bd0efe01152ab66a0d84be2686da2d28080a0fb27c1c13e354fe60d19fa2e7df2ea4688c656f4a6303445802e5dcf5fbdd8fe80808080808080808080b893f891a06740882da49a2e1ff126e3ba6fac2d2bb7d6dc8275585f3ede5b64c58bda2b338080a0402217be9e51170cd6a8a04dc94c9d0e8f8bb6c11f8b47a912334c1bba1653e6808080a0f3ef10a49d2164f97ae2fd436ec32ff14158ec1d3a435ac7a57b4388d8a43af680a06d650f375a4af0b5098322bb24a7cc880009bc7d1cdc5e829acb57f88f1f1e7180808080808080b884f882b83e2062623638323335393839323064623565323666306633636437626665343137366232343163383963333565373061323134373033643136356361616466b84064393730343035346366613034306461346630373835313765316164363863336535316130336334396565373664396532393863396664656665663238333634",
"0xcd3b3e76709fd984090ceffa27db7ece94d425d044bee7c68d18b03234915f94"
],
"out": [
true
]
}
},
"verifyExclusionProof": {
"existing key, different value": {
"in": [
"0x6b6579316161",
"0x6e6f742074686520636f72726563742076616c7565",
"0xf8a2a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080b0ef83206161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
true
]
},
"non-existent extension of a leaf": {
"in": [
"0x6b657931616162",
"0x736f6d65206172626974726172792076616c7565",
"0xf8a2a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080b0ef83206161aa303132333435363738393031323334353637383930313233343536373839303132333435363738397878",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
true
]
},
"non-existent extension of a branch": {
"in": [
"0x6b6579346464",
"0x736f6d65206172626974726172792076616c7565",
"0xf871a7e68416b65793a03101b4447781f1e6c51ce76c709274fc80bd064f3a58ff981b6015348a826386b847f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080",
"0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f"
],
"out": [
true
]
}
}
}
}
\ No newline at end of file
{
"tests": {
"toBool": {
"input bytes32 of 0": {
"in": ["0x0000000000000000000000000000000000000000000000000000000000000000"],
"out": [false]
},
"input bytes32 of 1": {
"in": ["0x0000000000000000000000000000000000000000000000000000000000000001"],
"out": [true]
},
"input bytes32 > 1": {
"in": ["0x1212121212121212121212121212121212121212121212121212121212121212"],
"out": [true]
},
"input bytes32 > 1, last byte 0": {
"in": ["0x1212121212121212121212121212121212121212121212121212121212121200"],
"out": [true]
}
},
"fromBool": {
"input false": {
"in": [false],
"out": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
},
"input true": {
"in": [true],
"out": ["0x0000000000000000000000000000000000000000000000000000000000000001"]
}
},
"toAddress": {
"input bytes32 address": {
"in": ["0x0000000000000000000000001212121212121212121212121212121212121212"],
"out": ["0x1212121212121212121212121212121212121212"]
},
"input full bytes32": {
"in": ["0x1212121212121212121212121212121212121212121212121212121212121212"],
"out": ["0x1212121212121212121212121212121212121212"]
}
},
"fromAddress": {
"input address": {
"in": ["0x1212121212121212121212121212121212121212"],
"out": ["0x1212121212121212121212121212121212121212000000000000000000000000"]
}
}
}
}
{
"tests": {
"concat": {
"two non-empty inputs": {
"in": ["0x1234", "0x5678"],
"out": ["0x12345678"]
},
"two empty inputs": {
"in": ["0x", "0x"],
"out": ["0x"]
},
"first empty, second non-empty": {
"in": ["0x1234", "0x"],
"out": ["0x1234"]
},
"first non-empty, second empty": {
"in": ["0x", "0x5678"],
"out": ["0x5678"]
}
},
"slice": {
"start zero, length = 0": {
"in": ["0x12345678", 0, 0],
"out": ["0x"]
},
"start zero, length = input.length": {
"in": ["0x12345678", 0, 4],
"out": ["0x12345678"]
},
"start zero, length > 0 and length < input.length": {
"in": ["0x12345678", 0, 2],
"out": ["0x1234"]
},
"start zero, length > input.length": {
"in": ["0x12345678", 0, 5],
"revert": "Read out of bounds"
},
"start > 0 and <= input.length, length = 0": {
"in": ["0x12345678", 2, 0],
"out": ["0x"]
},
"start > 0 and <= input.length, length = input.length - start": {
"in": ["0x12345678", 2, 2],
"out": ["0x5678"]
},
"start > 0 and <= input.length, length < input.length - start": {
"in": ["0x12345678", 2, 1],
"out": ["0x56"]
},
"start > 0 and <= input.length, length > input.length - start": {
"in": ["0x12345678", 2, 3],
"revert": "Read out of bounds"
},
"start > input.length": {
"in": ["0x12345678", 5, 1],
"revert":"Read out of bounds"
}
},
"toBytes32": {
"input.length < 32 bytes": {
"in": ["0x1234"],
"out": ["0x1234000000000000000000000000000000000000000000000000000000000000"]
},
"input.length = 32 bytes": {
"in": ["0x1234123412341234123412341234123412341234123412341234123412341234"],
"out": ["0x1234123412341234123412341234123412341234123412341234123412341234"]
},
"input.length > 32 bytes": {
"in": ["0x12341234123412341234123412341234123412341234123412341234123412345678567856785678567856785678567856785678567856785678567856785678"],
"out": ["0x1234123412341234123412341234123412341234123412341234123412341234"]
}
},
"toUint256": {
"input.length < 32 bytes": {
"in": ["0x000000000000000000000000000000000000000000000000000000001234"],
"out": [305397760]
},
"input.length = 32 bytes": {
"in": ["0x0000000000000000000000000000000000000000000000000000000000001234"],
"out": [4660]
},
"input.length > 32 bytes": {
"in": ["0x00000000000000000000000000000000000000000000000000000000000012345678567856785678567856785678567856785678567856785678567856785678"],
"out": [4660]
}
},
"toNibbles": {
"one byte": {
"in": ["0xff"],
"out": ["0x0f0f"]
},
"two bytes": {
"in": ["0xffff"],
"out": ["0x0f0f0f0f"]
},
"four bytes": {
"in": ["0xffffffff"],
"out": ["0x0f0f0f0f0f0f0f0f"]
}
},
"fromNibbles": {
"two nibbles": {
"in": ["0x0f0f"],
"out": ["0xff"]
},
"four nibbles": {
"in": ["0x0f0f0f0f"],
"out": ["0xffff"]
},
"eight nibbles": {
"in": ["0x0f0f0f0f0f0f0f0f"],
"out": ["0xffffffff"]
}
},
"equal": {
"same inputs": {
"in": ["0x1234", "0x1234"],
"out": [true]
},
"different inputs": {
"in": ["0x1234", "0x4567"],
"out": [false]
}
}
}
}
{
"tests": {
"recover": {
"standard hash, valid signature": {
"in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false,
"0xfc",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3",
"0x6c"
],
"out": [
"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff"
]
},
"standard hash, invalid v parameter": {
"in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false,
"0x00",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3",
"0x6c"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"standard hash, invalid r parameter": {
"in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false,
"0xfc",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3",
"0x6c"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"standard hash, invalid s parameter": {
"in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false,
"0xfc",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x6c"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"standard hash, invalid chainid parameter": {
"in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false,
"0xfc",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3",
"0x00"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"standard hash, invalid message": {
"in": [
"0x0000000000000000000000000000000000000000000000000000000000000000",
false,
"0xfc",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3",
"0x6c"
],
"out": [
"0x1397890fcfC2D7563Aa01cE93A217b9809D3447B"
]
},
"eth signed message hash, valid signature": {
"in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true,
"0x1c",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98",
"0x6c"
],
"out": [
"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff"
]
},
"eth signed message hash, invalid v parameter": {
"in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true,
"0x00",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98",
"0x6c"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"eth signed message hash, invalid r parameter": {
"in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true,
"0x1c",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98",
"0x6c"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"eth signed message hash, invalid s parameter": {
"in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true,
"0x1c",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x6c"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"eth signed message hash, invalid message": {
"in": [
"0x0000000000000000000000000000000000000000000000000000000000000000",
true,
"0x1c",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98",
"0x6c"
],
"out": [
"0xf8bd1421f9D28C8F58f33B25a6faB16F3f89b749"
]
}
}
}
}
{
"safetyCheckerTests": {
"tests": {
"single opcode blacklisted: ADDRESS": {
"in": "0x30",
"out": false
......@@ -72,6 +72,10 @@
"in": "0x32",
"out": false
},
"single opcode blacklisted: REVERT": {
"in": "0xfd",
"out": false
},
"single opcode blacklisted: SELFBALANCE": {
"in": "0x47",
"out": false
......@@ -748,6 +752,10 @@
"in": "0x0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b60306130306230303063303030306430303030306530303030303066303030303030306730303030303030306830303030303030303069303030303030303030306a30303030303030303030306b3030303030303030303030306c303030303030303030303030306d30303030303030303030303030306e3030303030303030303030303030306f303030303030303030303030303030307030303030303030303030303030303030307130303030303030303030303030303030303072303030303030303030303030303030303030307330303030303030303030303030303030303030307430303030303030303030303030303030303030303075303030303030303030303030303030303030303030307630303030303030303030303030303030303030303030307730303030303030303030303030303030303030303030303078303030303030303030303030303030303030303030303030307930303030303030303030303030303030303030303030303030307a3030303030303030303030303030303030303030303030303030307b303030303030303030303030303030303030303030303030303030307c30303030303030303030303030303030303030303030303030303030307d3030303030303030303030303030303030303030303030303030303030307e303030303030303030303030303030303030303030303030303030303030307f3030303030303030303030303030303030303030303030303030303030303030808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a432",
"out": false
},
"multiple opcodes blacklisted end: REVERT": {
"in": "0x0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b60306130306230303063303030306430303030306530303030303066303030303030306730303030303030306830303030303030303069303030303030303030306a30303030303030303030306b3030303030303030303030306c303030303030303030303030306d30303030303030303030303030306e3030303030303030303030303030306f303030303030303030303030303030307030303030303030303030303030303030307130303030303030303030303030303030303072303030303030303030303030303030303030307330303030303030303030303030303030303030307430303030303030303030303030303030303030303075303030303030303030303030303030303030303030307630303030303030303030303030303030303030303030307730303030303030303030303030303030303030303030303078303030303030303030303030303030303030303030303030307930303030303030303030303030303030303030303030303030307a3030303030303030303030303030303030303030303030303030307b303030303030303030303030303030303030303030303030303030307c30303030303030303030303030303030303030303030303030303030307d3030303030303030303030303030303030303030303030303030303030307e303030303030303030303030303030303030303030303030303030303030307f3030303030303030303030303030303030303030303030303030303030303030808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fd",
"out": false
},
"multiple opcodes blacklisted end: SELFBALANCE": {
"in": "0x0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b60306130306230303063303030306430303030306530303030303066303030303030306730303030303030306830303030303030303069303030303030303030306a30303030303030303030306b3030303030303030303030306c303030303030303030303030306d30303030303030303030303030306e3030303030303030303030303030306f303030303030303030303030303030307030303030303030303030303030303030307130303030303030303030303030303030303072303030303030303030303030303030303030307330303030303030303030303030303030303030307430303030303030303030303030303030303030303075303030303030303030303030303030303030303030307630303030303030303030303030303030303030303030307730303030303030303030303030303030303030303030303078303030303030303030303030303030303030303030303030307930303030303030303030303030303030303030303030303030307a3030303030303030303030303030303030303030303030303030307b303030303030303030303030303030303030303030303030303030307c30303030303030303030303030303030303030303030303030303030307d3030303030303030303030303030303030303030303030303030303030307e303030303030303030303030303030303030303030303030303030303030307f3030303030303030303030303030303030303030303030303030303030303030808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a447",
"out": false
......@@ -1048,10 +1056,6 @@
"in": "0x565bf3303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4f3303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4f3303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4",
"out": true
},
"valid alternating reachable/unreachable with REVERT": {
"in": "0x565bfd303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fd303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fd303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4",
"out": true
},
"valid alternating reachable/unreachable with INVALID": {
"in": "0x565bfe303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fe303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fe303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4",
"out": true
......@@ -1064,1364 +1068,44 @@
"in": "0x575bf3303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4f3303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4f3303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a430",
"out": false
},
"invalid alternating reachable/unreachable with REVERT": {
"in": "0x575bfd303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fd303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fd303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a430",
"out": false
},
"invalid alternating reachable/unreachable with INVALID": {
"in": "0x575bfe303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fe303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4fe303140f1f24641f0f5f4443b3c3f453a433247ff5455fa425b0102030405060708090a0b101112131415161718191a1b1c1d203435363738393d3e505152535758595a5b606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a430",
"out": false
},
"valid call": {
"in": "0x336000905af1",
"valid EM call": {
"in": "0x336000905af158601d0157586012013d600114573d6000803e3d6000fd5b60016000f35b",
"out": true
},
"invalid CALL with gas setter: ADD": {
"in": "0x3360009001f1",
"out": false
},
"invalid CALL with gas setter: MUL": {
"in": "0x3360009002f1",
"out": false
},
"invalid CALL with gas setter: SUB": {
"in": "0x3360009003f1",
"out": false
},
"invalid CALL with gas setter: DIV": {
"in": "0x3360009004f1",
"out": false
},
"invalid CALL with gas setter: SDIV": {
"in": "0x3360009005f1",
"out": false
},
"invalid CALL with gas setter: MOD": {
"in": "0x3360009006f1",
"out": false
},
"invalid CALL with gas setter: SMOD": {
"in": "0x3360009007f1",
"out": false
},
"invalid CALL with gas setter: ADDMOD": {
"in": "0x3360009008f1",
"out": false
},
"invalid CALL with gas setter: MULMOD": {
"in": "0x3360009009f1",
"out": false
},
"invalid CALL with gas setter: EXP": {
"in": "0x336000900af1",
"out": false
},
"invalid CALL with gas setter: SIGNEXTEND": {
"in": "0x336000900bf1",
"out": false
},
"invalid CALL with gas setter: LT": {
"in": "0x3360009010f1",
"out": false
},
"invalid CALL with gas setter: GT": {
"in": "0x3360009011f1",
"out": false
},
"invalid CALL with gas setter: SLT": {
"in": "0x3360009012f1",
"out": false
},
"invalid CALL with gas setter: SGT": {
"in": "0x3360009013f1",
"out": false
},
"invalid CALL with gas setter: EQ": {
"in": "0x3360009014f1",
"out": false
},
"invalid CALL with gas setter: ISZERO": {
"in": "0x3360009015f1",
"out": false
},
"invalid CALL with gas setter: AND": {
"in": "0x3360009016f1",
"out": false
},
"invalid CALL with gas setter: OR": {
"in": "0x3360009017f1",
"out": false
},
"invalid CALL with gas setter: XOR": {
"in": "0x3360009018f1",
"out": false
},
"invalid CALL with gas setter: NOT": {
"in": "0x3360009019f1",
"out": false
},
"invalid CALL with gas setter: BYTE": {
"in": "0x336000901af1",
"out": false
},
"invalid CALL with gas setter: SHL": {
"in": "0x336000901bf1",
"out": false
},
"invalid CALL with gas setter: SHR": {
"in": "0x336000901cf1",
"out": false
},
"invalid CALL with gas setter: SAR": {
"in": "0x336000901df1",
"out": false
},
"invalid CALL with gas setter: SHA3": {
"in": "0x3360009020f1",
"out": false
},
"invalid CALL with gas setter: CALLVALUE": {
"in": "0x3360009034f1",
"out": false
},
"invalid CALL with gas setter: CALLDATALOAD": {
"in": "0x3360009035f1",
"out": false
},
"invalid CALL with gas setter: CALLDATASIZE": {
"in": "0x3360009036f1",
"out": false
},
"invalid CALL with gas setter: CALLDATACOPY": {
"in": "0x3360009037f1",
"out": false
},
"invalid CALL with gas setter: CODESIZE": {
"in": "0x3360009038f1",
"out": false
},
"invalid CALL with gas setter: CODECOPY": {
"in": "0x3360009039f1",
"out": false
},
"invalid CALL with gas setter: RETURNDATASIZE": {
"in": "0x336000903df1",
"out": false
},
"invalid CALL with gas setter: RETURNDATACOPY": {
"in": "0x336000903ef1",
"out": false
},
"invalid CALL with gas setter: POP": {
"in": "0x3360009050f1",
"out": false
},
"invalid CALL with gas setter: MLOAD": {
"in": "0x3360009051f1",
"out": false
},
"invalid CALL with gas setter: MSTORE": {
"in": "0x3360009052f1",
"out": false
},
"invalid CALL with gas setter: MSTORE8": {
"in": "0x3360009053f1",
"out": false
},
"invalid CALL with gas setter: JUMPI": {
"in": "0x3360009057f1",
"out": false
},
"invalid CALL with gas setter: PC": {
"in": "0x3360009058f1",
"out": false
},
"invalid CALL with gas setter: MSIZE": {
"in": "0x3360009059f1",
"out": false
},
"invalid CALL with gas setter: JUMPDEST": {
"in": "0x336000905bf1",
"out": false
},
"invalid CALL with gas setter: PUSH1": {
"in": "0x336000906000f1",
"out": false
},
"invalid CALL with gas setter: PUSH2": {
"in": "0x33600090610000f1",
"out": false
},
"invalid CALL with gas setter: PUSH3": {
"in": "0x3360009062000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH4": {
"in": "0x336000906300000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH5": {
"in": "0x33600090640000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH6": {
"in": "0x3360009065000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH7": {
"in": "0x336000906600000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH8": {
"in": "0x33600090670000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH9": {
"in": "0x3360009068000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH10": {
"in": "0x336000906900000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH11": {
"in": "0x336000906a0000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH12": {
"in": "0x336000906b000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH13": {
"in": "0x336000906c00000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH14": {
"in": "0x336000906d0000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH15": {
"in": "0x336000906e000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH16": {
"in": "0x336000906f00000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH17": {
"in": "0x33600090700000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH18": {
"in": "0x3360009071000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH19": {
"in": "0x336000907200000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH20": {
"in": "0x33600090730000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH21": {
"in": "0x3360009074000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH22": {
"in": "0x336000907500000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH23": {
"in": "0x33600090760000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH24": {
"in": "0x3360009077000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH25": {
"in": "0x336000907800000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH26": {
"in": "0x33600090790000000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH27": {
"in": "0x336000907a000000000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH28": {
"in": "0x336000907b00000000000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH29": {
"in": "0x336000907c0000000000000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH30": {
"in": "0x336000907d000000000000000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH31": {
"in": "0x336000907e00000000000000000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: PUSH32": {
"in": "0x336000907f0000000000000000000000000000000000000000000000000000000000000000f1",
"out": false
},
"invalid CALL with gas setter: DUP1": {
"in": "0x3360009080f1",
"out": false
},
"invalid CALL with gas setter: DUP2": {
"in": "0x3360009081f1",
"out": false
},
"invalid CALL with gas setter: DUP3": {
"in": "0x3360009082f1",
"out": false
},
"invalid CALL with gas setter: DUP4": {
"in": "0x3360009083f1",
"out": false
},
"invalid CALL with gas setter: DUP5": {
"in": "0x3360009084f1",
"out": false
},
"invalid CALL with gas setter: DUP6": {
"in": "0x3360009085f1",
"out": false
},
"invalid CALL with gas setter: DUP7": {
"in": "0x3360009086f1",
"out": false
},
"invalid CALL with gas setter: DUP8": {
"in": "0x3360009087f1",
"out": false
},
"invalid CALL with gas setter: DUP9": {
"in": "0x3360009088f1",
"out": false
},
"invalid CALL with gas setter: DUP10": {
"in": "0x3360009089f1",
"out": false
},
"invalid CALL with gas setter: DUP11": {
"in": "0x336000908af1",
"out": false
},
"invalid CALL with gas setter: DUP12": {
"in": "0x336000908bf1",
"out": false
},
"invalid CALL with gas setter: DUP13": {
"in": "0x336000908cf1",
"out": false
},
"invalid CALL with gas setter: DUP14": {
"in": "0x336000908df1",
"out": false
},
"invalid CALL with gas setter: DUP15": {
"in": "0x336000908ef1",
"out": false
},
"invalid CALL with gas setter: DUP16": {
"in": "0x336000908ff1",
"out": false
},
"invalid CALL with gas setter: SWAP1": {
"in": "0x3360009090f1",
"out": false
},
"invalid CALL with gas setter: SWAP2": {
"in": "0x3360009091f1",
"out": false
},
"invalid CALL with gas setter: SWAP3": {
"in": "0x3360009092f1",
"out": false
},
"invalid CALL with gas setter: SWAP4": {
"in": "0x3360009093f1",
"out": false
},
"invalid CALL with gas setter: SWAP5": {
"in": "0x3360009094f1",
"out": false
},
"invalid CALL with gas setter: SWAP6": {
"in": "0x3360009095f1",
"out": false
},
"invalid CALL with gas setter: SWAP7": {
"in": "0x3360009096f1",
"out": false
},
"invalid CALL with gas setter: SWAP8": {
"in": "0x3360009097f1",
"out": false
},
"invalid CALL with gas setter: SWAP9": {
"in": "0x3360009098f1",
"out": false
},
"invalid CALL with gas setter: SWAP10": {
"in": "0x3360009099f1",
"out": false
},
"invalid CALL with gas setter: SWAP11": {
"in": "0x336000909af1",
"out": false
},
"invalid CALL with gas setter: SWAP12": {
"in": "0x336000909bf1",
"out": false
},
"invalid CALL with gas setter: SWAP13": {
"in": "0x336000909cf1",
"out": false
},
"invalid CALL with gas setter: SWAP14": {
"in": "0x336000909df1",
"out": false
},
"invalid CALL with gas setter: SWAP15": {
"in": "0x336000909ef1",
"out": false
},
"invalid CALL with gas setter: SWAP16": {
"in": "0x336000909ff1",
"out": false
},
"invalid CALL with gas setter: LOG0": {
"in": "0x33600090a0f1",
"out": false
},
"invalid CALL with gas setter: LOG1": {
"in": "0x33600090a1f1",
"out": false
},
"invalid CALL with gas setter: LOG2": {
"in": "0x33600090a2f1",
"out": false
},
"invalid CALL with gas setter: LOG3": {
"in": "0x33600090a3f1",
"out": false
},
"invalid CALL with gas setter: LOG4": {
"in": "0x33600090a4f1",
"out": false
},
"invalid CALL with value setter: ADD": {
"in": "0x3301905af1",
"out": false
},
"invalid CALL with value setter: MUL": {
"in": "0x3302905af1",
"out": false
},
"invalid CALL with value setter: SUB": {
"in": "0x3303905af1",
"out": false
},
"invalid CALL with value setter: DIV": {
"in": "0x3304905af1",
"out": false
},
"invalid CALL with value setter: SDIV": {
"in": "0x3305905af1",
"out": false
},
"invalid CALL with value setter: MOD": {
"in": "0x3306905af1",
"out": false
},
"invalid CALL with value setter: SMOD": {
"in": "0x3307905af1",
"out": false
},
"invalid CALL with value setter: ADDMOD": {
"in": "0x3308905af1",
"out": false
},
"invalid CALL with value setter: MULMOD": {
"in": "0x3309905af1",
"out": false
},
"invalid CALL with value setter: EXP": {
"in": "0x330a905af1",
"out": false
},
"invalid CALL with value setter: SIGNEXTEND": {
"in": "0x330b905af1",
"out": false
},
"invalid CALL with value setter: LT": {
"in": "0x3310905af1",
"out": false
},
"invalid CALL with value setter: GT": {
"in": "0x3311905af1",
"out": false
"valid identity precompile call": {
"in": "0x3350600060045af1",
"out": true
},
"invalid CALL with value setter: SLT": {
"in": "0x3312905af1",
"out": false
},
"invalid CALL with value setter: SGT": {
"in": "0x3313905af1",
"out": false
},
"invalid CALL with value setter: EQ": {
"in": "0x3314905af1",
"out": false
},
"invalid CALL with value setter: ISZERO": {
"in": "0x3315905af1",
"out": false
},
"invalid CALL with value setter: AND": {
"in": "0x3316905af1",
"out": false
},
"invalid CALL with value setter: OR": {
"in": "0x3317905af1",
"out": false
},
"invalid CALL with value setter: XOR": {
"in": "0x3318905af1",
"out": false
},
"invalid CALL with value setter: NOT": {
"in": "0x3319905af1",
"out": false
},
"invalid CALL with value setter: BYTE": {
"in": "0x331a905af1",
"out": false
},
"invalid CALL with value setter: SHL": {
"in": "0x331b905af1",
"out": false
},
"invalid CALL with value setter: SHR": {
"in": "0x331c905af1",
"out": false
},
"invalid CALL with value setter: SAR": {
"in": "0x331d905af1",
"out": false
},
"invalid CALL with value setter: SHA3": {
"in": "0x3320905af1",
"out": false
},
"invalid CALL with value setter: CALLVALUE": {
"in": "0x3334905af1",
"out": false
},
"invalid CALL with value setter: CALLDATALOAD": {
"in": "0x3335905af1",
"out": false
},
"invalid CALL with value setter: CALLDATASIZE": {
"in": "0x3336905af1",
"out": false
},
"invalid CALL with value setter: CALLDATACOPY": {
"in": "0x3337905af1",
"out": false
},
"invalid CALL with value setter: CODESIZE": {
"in": "0x3338905af1",
"out": false
},
"invalid CALL with value setter: CODECOPY": {
"in": "0x3339905af1",
"out": false
},
"invalid CALL with value setter: RETURNDATASIZE": {
"in": "0x333d905af1",
"out": false
},
"invalid CALL with value setter: RETURNDATACOPY": {
"in": "0x333e905af1",
"out": false
},
"invalid CALL with value setter: POP": {
"in": "0x3350905af1",
"out": false
},
"invalid CALL with value setter: MLOAD": {
"in": "0x3351905af1",
"out": false
},
"invalid CALL with value setter: MSTORE": {
"in": "0x3352905af1",
"out": false
},
"invalid CALL with value setter: MSTORE8": {
"in": "0x3353905af1",
"out": false
},
"invalid CALL with value setter: JUMPI": {
"in": "0x3357905af1",
"out": false
},
"invalid CALL with value setter: PC": {
"in": "0x3358905af1",
"out": false
},
"invalid CALL with value setter: MSIZE": {
"in": "0x3359905af1",
"out": false
},
"invalid CALL with value setter: GAS": {
"in": "0x335a905af1",
"out": false
},
"invalid CALL with value setter: JUMPDEST": {
"in": "0x335b905af1",
"out": false
},
"invalid CALL with value setter: PUSH2": {
"in": "0x33610000905af1",
"out": false
},
"invalid CALL with value setter: PUSH3": {
"in": "0x3362000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH4": {
"in": "0x336300000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH5": {
"in": "0x33640000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH6": {
"in": "0x3365000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH7": {
"in": "0x336600000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH8": {
"in": "0x33670000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH9": {
"in": "0x3368000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH10": {
"in": "0x336900000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH11": {
"in": "0x336a0000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH12": {
"in": "0x336b000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH13": {
"in": "0x336c00000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH14": {
"in": "0x336d0000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH15": {
"in": "0x336e000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH16": {
"in": "0x336f00000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH17": {
"in": "0x33700000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH18": {
"in": "0x3371000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH19": {
"in": "0x337200000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH20": {
"in": "0x33730000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH21": {
"in": "0x3374000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH22": {
"in": "0x337500000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH23": {
"in": "0x33760000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH24": {
"in": "0x3377000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH25": {
"in": "0x337800000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH26": {
"in": "0x33790000000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH27": {
"in": "0x337a000000000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH28": {
"in": "0x337b00000000000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH29": {
"in": "0x337c0000000000000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH30": {
"in": "0x337d000000000000000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH31": {
"in": "0x337e00000000000000000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: PUSH32": {
"in": "0x337f0000000000000000000000000000000000000000000000000000000000000000905af1",
"out": false
},
"invalid CALL with value setter: DUP1": {
"in": "0x3380905af1",
"out": false
},
"invalid CALL with value setter: DUP2": {
"in": "0x3381905af1",
"out": false
},
"invalid CALL with value setter: DUP3": {
"in": "0x3382905af1",
"out": false
},
"invalid CALL with value setter: DUP4": {
"in": "0x3383905af1",
"out": false
},
"invalid CALL with value setter: DUP5": {
"in": "0x3384905af1",
"out": false
},
"invalid CALL with value setter: DUP6": {
"in": "0x3385905af1",
"out": false
},
"invalid CALL with value setter: DUP7": {
"in": "0x3386905af1",
"out": false
},
"invalid CALL with value setter: DUP8": {
"in": "0x3387905af1",
"out": false
},
"invalid CALL with value setter: DUP9": {
"in": "0x3388905af1",
"out": false
},
"invalid CALL with value setter: DUP10": {
"in": "0x3389905af1",
"out": false
},
"invalid CALL with value setter: DUP11": {
"in": "0x338a905af1",
"out": false
},
"invalid CALL with value setter: DUP12": {
"in": "0x338b905af1",
"out": false
},
"invalid CALL with value setter: DUP13": {
"in": "0x338c905af1",
"out": false
},
"invalid CALL with value setter: DUP14": {
"in": "0x338d905af1",
"out": false
},
"invalid CALL with value setter: DUP15": {
"in": "0x338e905af1",
"out": false
},
"invalid CALL with value setter: DUP16": {
"in": "0x338f905af1",
"out": false
},
"invalid CALL with value setter: SWAP1": {
"in": "0x3390905af1",
"out": false
},
"invalid CALL with value setter: SWAP2": {
"in": "0x3391905af1",
"out": false
},
"invalid CALL with value setter: SWAP3": {
"in": "0x3392905af1",
"out": false
},
"invalid CALL with value setter: SWAP4": {
"in": "0x3393905af1",
"out": false
},
"invalid CALL with value setter: SWAP5": {
"in": "0x3394905af1",
"out": false
},
"invalid CALL with value setter: SWAP6": {
"in": "0x3395905af1",
"out": false
},
"invalid CALL with value setter: SWAP7": {
"in": "0x3396905af1",
"out": false
},
"invalid CALL with value setter: SWAP8": {
"in": "0x3397905af1",
"out": false
},
"invalid CALL with value setter: SWAP9": {
"in": "0x3398905af1",
"out": false
},
"invalid CALL with value setter: SWAP10": {
"in": "0x3399905af1",
"out": false
},
"invalid CALL with value setter: SWAP11": {
"in": "0x339a905af1",
"out": false
},
"invalid CALL with value setter: SWAP12": {
"in": "0x339b905af1",
"out": false
},
"invalid CALL with value setter: SWAP13": {
"in": "0x339c905af1",
"out": false
},
"invalid CALL with value setter: SWAP14": {
"in": "0x339d905af1",
"out": false
},
"invalid CALL with value setter: SWAP15": {
"in": "0x339e905af1",
"out": false
},
"invalid CALL with value setter: SWAP16": {
"in": "0x339f905af1",
"out": false
},
"invalid CALL with value setter: LOG0": {
"in": "0x33a0905af1",
"out": false
},
"invalid CALL with value setter: LOG1": {
"in": "0x33a1905af1",
"out": false
},
"invalid CALL with value setter: LOG2": {
"in": "0x33a2905af1",
"out": false
},
"invalid CALL with value setter: LOG3": {
"in": "0x33a3905af1",
"out": false
},
"invalid CALL with value setter: LOG4": {
"in": "0x33a4905af1",
"out": false
},
"invalid CALL with caller setter: ADD": {
"in": "0x016000905af1",
"out": false
},
"invalid CALL with caller setter: MUL": {
"in": "0x026000905af1",
"out": false
},
"invalid CALL with caller setter: SUB": {
"in": "0x036000905af1",
"out": false
},
"invalid CALL with caller setter: DIV": {
"in": "0x046000905af1",
"out": false
},
"invalid CALL with caller setter: SDIV": {
"in": "0x056000905af1",
"out": false
},
"invalid CALL with caller setter: MOD": {
"in": "0x066000905af1",
"out": false
},
"invalid CALL with caller setter: SMOD": {
"in": "0x076000905af1",
"out": false
},
"invalid CALL with caller setter: ADDMOD": {
"in": "0x086000905af1",
"out": false
},
"invalid CALL with caller setter: MULMOD": {
"in": "0x096000905af1",
"out": false
},
"invalid CALL with caller setter: EXP": {
"in": "0x0a6000905af1",
"out": false
},
"invalid CALL with caller setter: SIGNEXTEND": {
"in": "0x0b6000905af1",
"out": false
},
"invalid CALL with caller setter: LT": {
"in": "0x106000905af1",
"out": false
},
"invalid CALL with caller setter: GT": {
"in": "0x116000905af1",
"out": false
},
"invalid CALL with caller setter: SLT": {
"in": "0x126000905af1",
"out": false
},
"invalid CALL with caller setter: SGT": {
"in": "0x136000905af1",
"out": false
},
"invalid CALL with caller setter: EQ": {
"in": "0x146000905af1",
"out": false
},
"invalid CALL with caller setter: ISZERO": {
"in": "0x156000905af1",
"out": false
},
"invalid CALL with caller setter: AND": {
"in": "0x166000905af1",
"out": false
},
"invalid CALL with caller setter: OR": {
"in": "0x176000905af1",
"out": false
},
"invalid CALL with caller setter: XOR": {
"in": "0x186000905af1",
"out": false
},
"invalid CALL with caller setter: NOT": {
"in": "0x196000905af1",
"out": false
},
"invalid CALL with caller setter: BYTE": {
"in": "0x1a6000905af1",
"out": false
},
"invalid CALL with caller setter: SHL": {
"in": "0x1b6000905af1",
"out": false
},
"invalid CALL with caller setter: SHR": {
"in": "0x1c6000905af1",
"out": false
},
"invalid CALL with caller setter: SAR": {
"in": "0x1d6000905af1",
"out": false
},
"invalid CALL with caller setter: SHA3": {
"in": "0x206000905af1",
"out": false
},
"invalid CALL with caller setter: CALLVALUE": {
"in": "0x346000905af1",
"out": false
},
"invalid CALL with caller setter: CALLDATALOAD": {
"in": "0x356000905af1",
"out": false
},
"invalid CALL with caller setter: CALLDATASIZE": {
"in": "0x366000905af1",
"out": false
},
"invalid CALL with caller setter: CALLDATACOPY": {
"in": "0x376000905af1",
"out": false
},
"invalid CALL with caller setter: CODESIZE": {
"in": "0x386000905af1",
"out": false
},
"invalid CALL with caller setter: CODECOPY": {
"in": "0x396000905af1",
"out": false
},
"invalid CALL with caller setter: RETURNDATASIZE": {
"in": "0x3d6000905af1",
"out": false
},
"invalid CALL with caller setter: RETURNDATACOPY": {
"in": "0x3e6000905af1",
"out": false
},
"invalid CALL with caller setter: POP": {
"in": "0x506000905af1",
"out": false
},
"invalid CALL with caller setter: MLOAD": {
"in": "0x516000905af1",
"out": false
},
"invalid CALL with caller setter: MSTORE": {
"in": "0x526000905af1",
"out": false
},
"invalid CALL with caller setter: MSTORE8": {
"in": "0x536000905af1",
"out": false
},
"invalid CALL with caller setter: JUMPI": {
"in": "0x576000905af1",
"out": false
},
"invalid CALL with caller setter: PC": {
"in": "0x586000905af1",
"out": false
},
"invalid CALL with caller setter: MSIZE": {
"in": "0x596000905af1",
"out": false
},
"invalid CALL with caller setter: GAS": {
"in": "0x5a6000905af1",
"out": false
},
"invalid CALL with caller setter: JUMPDEST": {
"in": "0x5b6000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH1": {
"in": "0x60006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH2": {
"in": "0x6100006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH3": {
"in": "0x620000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH4": {
"in": "0x63000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH5": {
"in": "0x6400000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH6": {
"in": "0x650000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH7": {
"in": "0x66000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH8": {
"in": "0x6700000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH9": {
"in": "0x680000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH10": {
"in": "0x69000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH11": {
"in": "0x6a00000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH12": {
"in": "0x6b0000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH13": {
"in": "0x6c000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH14": {
"in": "0x6d00000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH15": {
"in": "0x6e0000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH16": {
"in": "0x6f000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH17": {
"in": "0x7000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH18": {
"in": "0x710000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH19": {
"in": "0x72000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH20": {
"in": "0x7300000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH21": {
"in": "0x740000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH22": {
"in": "0x75000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH23": {
"in": "0x7600000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH24": {
"in": "0x770000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH25": {
"in": "0x78000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH26": {
"in": "0x7900000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH27": {
"in": "0x7a0000000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH28": {
"in": "0x7b000000000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH29": {
"in": "0x7c00000000000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH30": {
"in": "0x7d0000000000000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH31": {
"in": "0x7e000000000000000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: PUSH32": {
"in": "0x7f00000000000000000000000000000000000000000000000000000000000000006000905af1",
"out": false
},
"invalid CALL with caller setter: DUP1": {
"in": "0x806000905af1",
"out": false
},
"invalid CALL with caller setter: DUP2": {
"in": "0x816000905af1",
"out": false
},
"invalid CALL with caller setter: DUP3": {
"in": "0x826000905af1",
"out": false
},
"invalid CALL with caller setter: DUP4": {
"in": "0x836000905af1",
"out": false
},
"invalid CALL with caller setter: DUP5": {
"in": "0x846000905af1",
"out": false
},
"invalid CALL with caller setter: DUP6": {
"in": "0x856000905af1",
"out": false
},
"invalid CALL with caller setter: DUP7": {
"in": "0x866000905af1",
"out": false
},
"invalid CALL with caller setter: DUP8": {
"in": "0x876000905af1",
"out": false
},
"invalid CALL with caller setter: DUP9": {
"in": "0x886000905af1",
"out": false
},
"invalid CALL with caller setter: DUP10": {
"in": "0x896000905af1",
"out": false
},
"invalid CALL with caller setter: DUP11": {
"in": "0x8a6000905af1",
"out": false
},
"invalid CALL with caller setter: DUP12": {
"in": "0x8b6000905af1",
"out": false
},
"invalid CALL with caller setter: DUP13": {
"in": "0x8c6000905af1",
"out": false
},
"invalid CALL with caller setter: DUP14": {
"in": "0x8d6000905af1",
"out": false
},
"invalid CALL with caller setter: DUP15": {
"in": "0x8e6000905af1",
"out": false
},
"invalid CALL with caller setter: DUP16": {
"in": "0x8f6000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP1": {
"in": "0x906000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP2": {
"in": "0x916000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP3": {
"in": "0x926000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP4": {
"in": "0x936000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP5": {
"in": "0x946000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP6": {
"in": "0x956000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP7": {
"in": "0x966000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP8": {
"in": "0x976000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP9": {
"in": "0x986000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP10": {
"in": "0x996000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP11": {
"in": "0x9a6000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP12": {
"in": "0x9b6000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP13": {
"in": "0x9c6000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP14": {
"in": "0x9d6000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP15": {
"in": "0x9e6000905af1",
"out": false
},
"invalid CALL with caller setter: SWAP16": {
"in": "0x9f6000905af1",
"out": false
},
"invalid CALL with caller setter: LOG0": {
"in": "0xa06000905af1",
"out": false
},
"invalid CALL with caller setter: LOG1": {
"in": "0xa16000905af1",
"out": false
"valid EM call, then valid identity precompile call": {
"in": "0x336000905af158601d0157586012013d600114573d6000803e3d6000fd5b60016000f35b3350600060045af1",
"out": true
},
"invalid CALL with caller setter: LOG2": {
"in": "0xa26000905af1",
"valid EM call, then invalid identity precompile call": {
"in": "0x336000905af158601d0157586012013d600114573d6000803e3d6000fd5b60016000f35b3350600060035af1",
"out": false
},
"invalid CALL with caller setter: LOG3": {
"in": "0xa36000905af1",
"valid EM call, then invalid opcode (SLOAD)": {
"in": "0x336000905af158601d0157586012013d600114573d6000803e3d6000fd5b60016000f35b54",
"out": false
},
"invalid CALL with caller setter: LOG4": {
"in": "0xa46000905af1",
"valid identity precompile call, then invalid opcode (SLOAD)": {
"in": "0x3350600060045af154",
"out": false
},
"invalid CALL with non-zero value": {
"in": "0x336001905af1",
"invalid EM call (missing final byte)": {
"in": "0x336000905af158601d0157586012013d600114573d6000803e3d6000fd5b60016000f3",
"out": false
},
"invalid call to non-ExecutionManager address": {
"in": "0x73ffffffffffffffffffffffffffffffffffffffff6000905af1",
"invalid identity precompile call (missing final byte)": {
"in": "0x3350600060045a",
"out": false
},
"invalid CALL without SWAP1": {
"in": "0x6000335af1",
"invalid identity precompile call (First 2 bytes are PUSH2ed)": {
"in": "0x613350600060045af1",
"out": false
}
}
......
export const toHexString = (buf: Buffer | string): string => {
return '0x' + fromHexString(buf).toString('hex')
}
export const fromHexString = (str: string | Buffer): Buffer => {
if (typeof str === 'string' && str.startsWith('0x')) {
return Buffer.from(str.slice(2), 'hex')
}
return Buffer.from(str)
}
/* External Imports */
import fsExtra from 'fs-extra'
import { internalTask } from '@nomiclabs/buidler/config'
import { pluralize } from '@nomiclabs/buidler/internal/util/strings'
import {
getArtifactFromContractOutput,
saveArtifact,
} from '@nomiclabs/buidler/internal/artifacts'
import {
TASK_COMPILE_GET_COMPILER_INPUT,
TASK_BUILD_ARTIFACTS,
TASK_COMPILE_GET_SOURCE_PATHS,
TASK_COMPILE_CHECK_CACHE,
TASK_COMPILE_COMPILE,
} from '@nomiclabs/buidler/builtin-tasks/task-names'
internalTask(
TASK_COMPILE_GET_COMPILER_INPUT,
async (_, { config, run }, runSuper) => {
const input = await runSuper()
// Insert the "storageLayout" input option.
input.settings.outputSelection['*']['*'].push('storageLayout')
return input
}
)
internalTask(TASK_BUILD_ARTIFACTS).setAction(
async ({ force }, { config, run }) => {
const sources = await run(TASK_COMPILE_GET_SOURCE_PATHS)
if (sources.length === 0) {
console.log('No Solidity source file available.')
return
}
const isCached: boolean = await run(TASK_COMPILE_CHECK_CACHE, { force })
if (isCached) {
console.log(
'All contracts have already been compiled, skipping compilation.'
)
return
}
const compilationOutput = await run(TASK_COMPILE_COMPILE)
if (compilationOutput === undefined) {
return
}
await fsExtra.ensureDir(config.paths.artifacts)
let numberOfContracts = 0
for (const file of Object.values<any>(compilationOutput.contracts)) {
for (const [contractName, contractOutput] of Object.entries(file)) {
const artifact: any = getArtifactFromContractOutput(
contractName,
contractOutput
)
numberOfContracts += 1
// Only difference here, set the "storageLayout" field of the artifact.
artifact.storageLayout = (contractOutput as any).storageLayout
await saveArtifact(config.paths.artifacts, artifact)
}
}
console.log(
'Compiled',
numberOfContracts,
pluralize(numberOfContracts, 'contract'),
'successfully'
)
}
)
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { hexZeroPad } from 'ethers/lib/utils'
export const encodeRevertData = (
flag: number,
......@@ -15,13 +15,11 @@ export const encodeRevertData = (
}
export const decodeRevertData = (revertData: string): any => {
// const length: number = revertData.length/2 - 1
// const reencodedRevertData = '0x' + hexZeroPad('0x' + length.toString(16), 32) + revertData.slice(2)
const decoded = ethers.utils.defaultAbiCoder.decode(
['uint256', 'uint256', 'uint256', 'bytes'],
revertData
)
console.log(`flag is: ${decoded[0].toNumber()}`)
return (
'[revertFlag:' +
Object.keys(REVERT_FLAGS)[decoded[0]] +
......
import * as path from 'path'
import bre from '@nomiclabs/buidler'
import { Compiler } from '@nomiclabs/buidler/internal/solidity/compiler'
export interface SolidityCompiler {
version: () => string
compile: any
}
export interface ContractSource {
path: string
content: string
}
export const getDefaultCompiler = async (): Promise<SolidityCompiler> => {
const compiler = new Compiler(
bre.config.solc.version,
path.join(bre.config.paths.cache, 'compilers')
)
return compiler.getSolc()
}
export const compile = async (
sources: ContractSource[],
compiler?: SolidityCompiler
): Promise<any> => {
compiler = compiler || (await getDefaultCompiler())
const compilerInput = {
language: 'Solidity',
sources: sources.reduce((parsed, source) => {
parsed[source.path] = {
content: source.content,
}
return parsed
}, {}),
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
}
return JSON.parse(compiler.compile(JSON.stringify(compilerInput)))
}
/* External Imports */
import { ethers } from 'ethers'
import { defaultAccounts } from 'ethereum-waffle'
import xor from 'buffer-xor'
/* Internal Imports */
import { makeHexString, makeAddress } from './byte-utils'
import { makeHexString, makeAddress, fromHexString, toHexString } from './utils'
export const DEFAULT_ACCOUNTS = defaultAccounts
export const DEFAULT_ACCOUNTS_BUIDLER = defaultAccounts.map((account) => {
......@@ -13,7 +14,9 @@ export const DEFAULT_ACCOUNTS_BUIDLER = defaultAccounts.map((account) => {
}
})
export const GAS_LIMIT = 1_000_000_000
export const OVM_TX_GAS_LIMIT = 10_000_000
export const RUN_OVM_TEST_GAS = 20_000_000
export const FORCE_INCLUSION_PERIOD_SECONDS = 600
export const NULL_BYTES32 = makeHexString('00', 32)
export const NON_NULL_BYTES32 = makeHexString('11', 32)
......@@ -22,3 +25,20 @@ export const NON_ZERO_ADDRESS = makeAddress('11')
export const VERIFIED_EMPTY_CONTRACT_HASH =
'0x00004B1DC0DE000000004B1DC0DE000000004B1DC0DE000000004B1DC0DE0000'
export const NUISANCE_GAS_COSTS = {
NUISANCE_GAS_SLOAD: 20000,
NUISANCE_GAS_SSTORE: 20000,
MIN_NUISANCE_GAS_PER_CONTRACT: 30000,
NUISANCE_GAS_PER_CONTRACT_BYTE: 100,
MIN_GAS_FOR_INVALID_STATE_ACCESS: 30000,
}
// TODO: get this exported/imported somehow in a way that we can do math on it. unfortunately using require('.....artifacts/contract.json') is erroring...
export const Helper_TestRunner_BYTELEN = 3654
export const STORAGE_XOR =
'0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef'
export const getStorageXOR = (key: string): string => {
return toHexString(xor(fromHexString(key), fromHexString(STORAGE_XOR)))
}
import { create2Tests } from './json/create2.test.json'
import { rlpTests } from './json/rlp.test.json'
import { safetyCheckerTests } from './json/safety-checker.test.json'
export const RLP_TEST_JSON = rlpTests
export const CREATE2_TEST_JSON = create2Tests
export const SAFETY_CHECKER_TEST_JSON = safetyCheckerTests
/* External Imports */
import { BigNumber } from 'ethers'
/* Internal Imports */
import { DUMMY_BYTES32 } from './bytes32'
import { ZERO_ADDRESS, NON_ZERO_ADDRESS } from '../constants'
import { makeAddress } from '../byte-utils'
import { makeAddress } from '../utils'
import { OVMAccount } from '../types/ovm-types'
export const DUMMY_ACCOUNTS: Array<{
......@@ -11,8 +14,8 @@ export const DUMMY_ACCOUNTS: Array<{
{
address: makeAddress('12'),
data: {
nonce: 123,
balance: 456,
nonce: BigNumber.from(123),
balance: BigNumber.from(456),
storageRoot: DUMMY_BYTES32[0],
codeHash: DUMMY_BYTES32[1],
ethAddress: ZERO_ADDRESS,
......@@ -21,8 +24,8 @@ export const DUMMY_ACCOUNTS: Array<{
{
address: makeAddress('21'),
data: {
nonce: 321,
balance: 654,
nonce: BigNumber.from(321),
balance: BigNumber.from(654),
storageRoot: DUMMY_BYTES32[2],
codeHash: DUMMY_BYTES32[3],
ethAddress: NON_ZERO_ADDRESS,
......
import { NULL_BYTES32 } from '../constants'
export const DUMMY_BATCH_HEADERS = [
{
batchIndex: 0,
batchRoot: NULL_BYTES32,
batchSize: 0,
prevTotalElements: 0,
extraData: NULL_BYTES32,
},
]
export const DUMMY_BATCH_PROOFS = [
{
index: 0,
siblings: [NULL_BYTES32],
},
]
......@@ -2,3 +2,5 @@ export * from './accounts'
export * from './bytes32'
export * from './context'
export * from './bytecode'
export * from './batches'
export * from './transactions'
import { ZERO_ADDRESS, NULL_BYTES32 } from '../constants'
export const DUMMY_OVM_TRANSACTIONS = [
{
timestamp: 0,
number: 0,
l1QueueOrigin: 0,
l1Txorigin: ZERO_ADDRESS,
entrypoint: ZERO_ADDRESS,
gasLimit: 0,
data: NULL_BYTES32,
},
]
export * from './dummy'
export * from './types'
export * from './constants'
export * from './proxy'
export * from './mocks'
export * from './buffer-utils'
export * from './byte-utils'
export * from './resolver'
export * from './utils'
export * from './codec'
export * from './data'
export * from './test-utils'
export * from './test-runner'
export * from './trie'
export * from './mock-contract.types'
export * from './mock-generation'
/* External Imports */
import bre from '@nomiclabs/buidler'
import { ethers, Contract, ContractFactory } from 'ethers'
/* Internal Imports */
import { toHexString, fromHexString } from '../buffer-utils'
import { MockContract, MockContractFunction } from './mock-contract.types'
/**
* Binds logic to the buidler node that checks for calls to mock contracts and
* replaces return values. Runs once as to avoid repeatedly hijacking functions
* for each new mock contract.
*/
export const bindMockWatcherToVM = (): void => {
const node = bre.network.provider['_node' as any]
// No need to bind here if we've already done so.
if (node.__calls) {
return
}
const vmTracer = node['_vmTracer' as any]
const vm = node['_vm' as any]
// Set up some things we'll need for later.
let txid: string
const messages: Array<{
address: string
sighash: string
calldata: string
}> = []
node.__calls = {}
node.__contracts = {}
// Modify the vm.runTx function to capture an ID for each transaction.
const originalRunTx = vm.runTx.bind(vm)
function modifiedRunTx(opts: any): Promise<any> {
// Buidler runs transactions multiple times (e.g., for gas estimation).
// Here we're computing a unique ID for each transaction (based on sender,
// nonce, and transaction data) so that we don't log calls multiple times.
txid = ethers.utils.keccak256(
'0x' +
opts.tx._from.toString('hex') +
opts.tx.nonce.toString('hex') +
opts.tx.data.toString('hex')
)
// Wipe the calls for this txid to avoid duplicate results.
node.__calls[txid] = {}
return originalRunTx(opts)
}
vm['runTx' as any] = modifiedRunTx.bind(vm)
// Modify the pre-message handler to capture calldata.
const originalBeforeMessageHandler = vmTracer['_beforeMessageHandler' as any]
function modifiedBeforeMessageHandler(message: any, next: any) {
// We only care about capturing if we're sending to one of our mocks.
const address = message.to
? toHexString(message.to).toLowerCase()
: undefined
const contract = node.__contracts[address]
if (address && contract) {
const calldata = toHexString(message.data.slice(4))
let sighash = toHexString(message.data.slice(0, 4))
if (contract.__sigmap) {
sighash = contract.__sigmap[sighash]
message.data.write(sighash.slice(2), 0, 4, 'hex')
}
// Store the message for use in the post-message handler.
messages.push({
address,
sighash,
calldata,
})
// Basic existence checks.
if (!node.__calls[txid][address]) {
node.__calls[txid][address] = {}
}
if (!node.__calls[txid][address][sighash]) {
node.__calls[txid][address][sighash] = []
}
// Add the data to the per-sighash array.
node.__calls[txid][address][sighash].push(toHexString(message.data))
}
originalBeforeMessageHandler(message, next)
}
// Modify the post-message handler to insert the correct return data.
const originalAfterMessageHandler = vmTracer['_afterMessageHandler' as any]
async function modifiedAfterMessageHandler(result: any, next: any) {
// We don't need to do anything if we haven't stored any mock messages.
if (messages.length > 0) {
// We need to look at the messages backwards since the first result will
// correspond to the last message on the stack.
const message = messages.pop()
const contract: Contract = node.__contracts[message.address]
const fn: MockContractFunction = contract.__fns[message.sighash]
// Compute our return values.
const inputParams = contract.__spec
? contract.__spec.interface.decodeFunctionData(
fn.functionName,
contract.__spec.interface.getSighash(fn.functionName) +
message.calldata.slice(2)
)
: ethers.utils.defaultAbiCoder.decode(fn.inputTypes, message.calldata)
const returnValues = Array.isArray(fn.returnValues)
? fn.returnValues
: await fn.returnValues(...inputParams)
const returnBuffer = fromHexString(
contract.__spec
? contract.__spec.interface.encodeFunctionResult(
fn.functionName,
returnValues
)
: ethers.utils.defaultAbiCoder.encode(fn.outputTypes, returnValues)
)
// Set the return value to match our computed value.
result.execResult.returnValue = returnBuffer
}
originalAfterMessageHandler(result, next)
}
// Disable tracing to remove the old handlers before adding new ones.
vmTracer.disableTracing()
vmTracer['_beforeMessageHandler' as any] = modifiedBeforeMessageHandler.bind(
vmTracer
)
vmTracer['_afterMessageHandler' as any] = modifiedAfterMessageHandler.bind(
vmTracer
)
vmTracer.enableTracing()
}
/**
* Binds a mock contract to the VM and inserts necessary functions.
* @param mock Mock contract to bind.
* @param fns Contract functions associated with the mock.
*/
export const bindMockContractToVM = (
mock: MockContract,
fns: MockContractFunction[],
spec: MockContractFunction[] | Contract | ContractFactory
): void => {
const node = bre.network.provider['_node' as any]
node.__contracts[mock.address.toLowerCase()] = mock
const getCalls = (functionName: string): string[] => {
const calls: {
[sighash: string]: string[]
} = {}
for (const txid of Object.keys(node.__calls)) {
for (const address of Object.keys(node.__calls[txid])) {
if (address === mock.address.toLowerCase()) {
for (const sighash of Object.keys(node.__calls[txid][address])) {
const txcalls = node.__calls[txid][address][sighash]
calls[sighash] = calls[sighash]
? calls[sighash].concat(txcalls)
: txcalls
}
}
}
}
const sig = mock.interface.getSighash(functionName)
return calls[sig] || []
}
if (!Array.isArray(spec)) {
;(mock as any).__spec = spec
;(mock as any).__sigmap = Object.keys(mock.interface.functions).reduce(
(sigmap, fn) => {
fn = fn.split('(')[0]
sigmap[spec.interface.getSighash(fn)] = mock.interface.getSighash(fn)
return sigmap
},
{}
)
}
;(mock as any).getCallCount = (functionName: string): number => {
return getCalls(functionName).length
}
;(mock as any).getCallData = (
functionName: string,
callIndex: number
): any[] => {
const calls = getCalls(functionName)
if (calls.length <= callIndex) {
throw new Error('Provided function call index does not exist.')
}
const iface = mock.__spec ? mock.__spec.interface : mock.interface
const calldata = iface.getSighash(functionName) + calls[callIndex].slice(10)
return iface.decodeFunctionData(functionName, calldata).map((element) => {
return element
})
}
;(mock as any).setReturnValues = (
functionName: string,
returnValues: any[] | ((...params: any[]) => any[])
): void => {
mock.__fns[
mock.interface.getSighash(functionName)
].returnValues = returnValues
}
;(mock as any).__fns = fns.reduce((fnmap, fn) => {
fnmap[mock.interface.getSighash(fn.functionName)] = fn
return fnmap
}, {})
}
/* External Imports */
import { Contract } from 'ethers'
export interface MockContractFunction {
functionName: string
inputTypes?: string[]
outputTypes?: string[]
returnValues?: any[] | ((...params: any[]) => any[] | Promise<any>)
}
export interface MockContract extends Contract {
getCallCount: (functionName: string) => number
getCallData: (functionName: string, callIndex: number) => any[]
setReturnValues: (
functionName: string,
returnValues: any[] | ((...params: any[]) => any[])
) => void
__fns: {
[sighash: string]: MockContractFunction
}
}
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Signer, Contract, ContractFactory } from 'ethers'
import { FunctionFragment, ParamType } from 'ethers/lib/utils'
/* Internal Imports */
import { MockContract, MockContractFunction } from './mock-contract.types'
import { bindMockContractToVM, bindMockWatcherToVM } from './mock-binding'
import { SolidityCompiler, getDefaultCompiler, compile } from '../compilation'
/**
* Generates contract code for a mock contract.
* @param fns Mock contract function definitions.
* @param contractName Name for the contract.
* @param compilerVersion Compiler version being used.
* @returns Contract code.
*/
const getSolidityMockContractCode = (
fns: MockContractFunction[],
contractName: string,
compilerVersion: string
): string => {
return `
pragma solidity ${compilerVersion};
contract ${contractName} {
${fns
.map((fn) => {
return `
function ${fn.functionName}(${fn.inputTypes
.map((inputType, idx) => {
return `${inputType} _${idx}`
})
.join(', ')})
public
{
return;
}
`
})
.join('\n')}
}
`
}
/**
* Checks that a mock contract function definition is valid.
* @param fn Mock contract function definition.
* @returns Whether or not the function is valid.
*/
const isValidMockContractFunction = (fn: MockContractFunction): boolean => {
return (
fn.inputTypes &&
fn.outputTypes &&
fn.returnValues &&
(!Array.isArray(fn.returnValues) ||
fn.outputTypes.length === fn.returnValues.length)
)
}
/**
* Basic sanitization for mock function definitions
* @param fn Mock contract function definition to sanitize.
* @returns Sanitized definition.
*/
export const sanitizeMockContractFunction = (
fn: MockContractFunction
): MockContractFunction => {
const sanitized = {
functionName: fn.functionName,
inputTypes: fn.inputTypes || [],
outputTypes: fn.outputTypes || [],
returnValues: fn.returnValues || [],
}
if (!isValidMockContractFunction(sanitized)) {
throw new Error(
'Provided MockContract function is invalid. Please check your mock definition.'
)
}
return sanitized
}
/**
* Basic sanitization for mock function definitions
* @param fns Mock contract function definitions to sanitize.
* @returns Sanitized definitions.
*/
const sanitizeMockContractFunctions = (
fns: MockContractFunction[]
): MockContractFunction[] => {
return fns.map((fn) => {
return sanitizeMockContractFunction(fn)
})
}
/**
* Gets mock return values for a set of output types.
* @param outputTypes Output types as ethers param types.
* @returns Mock return values.
*/
const getMockReturnValues = (outputTypes: ParamType[]): string[] => {
return outputTypes.map((outputType) => {
return outputType.type === outputType.baseType
? '0x' + '00'.repeat(32)
: '0x' + '00'.repeat(64)
})
}
/**
* Converts an ethers function fragment to a mock function.
* @param fn Function fragment to convert.
* @returns Generated mock function.
*/
const getMockFunctionFromFragment = (
fn: FunctionFragment
): MockContractFunction => {
return {
functionName: fn.name,
inputTypes: [],
outputTypes: [],
returnValues: getMockReturnValues(fn.outputs),
}
}
/**
* Generates mock functions from a contract spec.
* @param spec Contract or factory used as the spec.
* @returns Array of mock functions.
*/
const getFnsFromContractSpec = (
spec: Contract | ContractFactory
): MockContractFunction[] => {
return Object.values(spec.interface.functions)
.filter((fn) => {
return fn.type === 'function'
})
.map((fn) => {
return getMockFunctionFromFragment(fn)
})
}
/**
* Generates a mock contract for testing.
* @param spec Mock contract function definitions or contract to base on.
* @param signer Signer to use to deploy the mock.
* @param compiler Optional compiler instance to use.
* @returns Generated mock contract instance.
*/
export const getMockContract = async (
spec: MockContractFunction[] | Contract | ContractFactory | string,
signer?: Signer,
compiler?: SolidityCompiler
): Promise<MockContract> => {
if (typeof spec === 'string') {
spec = await ethers.getContractFactory(spec)
}
if (!Array.isArray(spec)) {
signer = signer || spec.signer
}
if (!signer) {
throw new Error('You must provide a signer.')
}
compiler = compiler || (await getDefaultCompiler())
const fns = Array.isArray(spec)
? sanitizeMockContractFunctions(spec)
: getFnsFromContractSpec(spec)
const contractName = 'MockContract'
const contractPath = contractName + '.sol'
const contractCode = getSolidityMockContractCode(
fns,
contractName,
'^' + compiler.version().split('+')[0]
)
const compilerOutput = await compile(
[
{
path: contractPath,
content: contractCode,
},
],
compiler
)
const MockContractJSON = compilerOutput.contracts[contractPath][contractName]
const MockContractFactory = new ethers.ContractFactory(
MockContractJSON.abi,
MockContractJSON.evm.bytecode.object,
signer
)
const originalDefinePropertyFn = Object.defineProperty
Object.defineProperty = (object: any, name: string, props: any): void => {
if (props.writable === false) {
props.writable = true
}
originalDefinePropertyFn(object, name, props)
}
const mock = (await MockContractFactory.deploy()) as MockContract
Object.defineProperty = originalDefinePropertyFn
bindMockWatcherToVM()
bindMockContractToVM(mock, fns, spec)
return mock
}
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
export const makeProxies = async (
Proxy_Manager: Contract,
names: string[]
): Promise<void> => {
for (const name of names) {
if (await Proxy_Manager['hasProxy(string)'](name)) {
continue
}
const Factory__Proxy_Forwarder = await ethers.getContractFactory(
'Proxy_Forwarder'
)
const Proxy_Forwarder = await Factory__Proxy_Forwarder.deploy(
Proxy_Manager.address
)
await Proxy_Manager.setProxy(name, Proxy_Forwarder.address)
}
}
export const setProxyTarget = async (
Proxy_Manager: Contract,
name: string,
target: Contract
): Promise<void> => {
await makeProxies(Proxy_Manager, [name])
await Proxy_Manager.setTarget(name, target.address)
}
export const getProxyManager = async (): Promise<Contract> => {
return (await ethers.getContractFactory('Proxy_Manager')).deploy()
}
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
export const setProxyTarget = async (
AddressManager: Contract,
name: string,
target: Contract
): Promise<void> => {
const SimpleProxy: Contract = await (
await ethers.getContractFactory('Helper_SimpleProxy')
).deploy()
await SimpleProxy.setTarget(target.address)
await AddressManager.setAddress(name, SimpleProxy.address)
}
export const makeAddressManager = async (): Promise<Contract> => {
return (await ethers.getContractFactory('Lib_AddressManager')).deploy()
}
import bre, { ethers } from '@nomiclabs/buidler'
import { Contract, BigNumber, ContractFactory } from 'ethers'
import { keccak256, defaultAbiCoder } from 'ethers/lib/utils'
import { remove0x } from '../byte-utils'
import { readArtifact } from '@nomiclabs/buidler/internal/artifacts'
const getFlattenedKeys = (depth: number, value: any): string[] => {
if (depth === 0) {
return []
}
let keys = Object.keys(value)
if (depth > 1) {
keys = keys.concat(getFlattenedKeys(depth - 1, Object.values(value)[0]))
}
return keys
}
const toHexString32 = (
value: string | number | BigNumber | boolean
): string => {
if (typeof value === 'string') {
return '0x' + remove0x(value).padStart(64, '0').toLowerCase()
} else if (typeof value === 'boolean') {
return toHexString32(value ? 1 : 0)
} else {
return toHexString32(BigNumber.from(value).toHexString())
}
}
const getFlattenedValues = (depth: number, value: any): any[] => {
if (depth > 0) {
return getFlattenedValues(depth - 1, Object.values(value)[0])
}
if (typeof value === 'object' && value !== null) {
return Object.keys(value).map((key) => {
return {
label: key,
value: toHexString32(value[key]),
}
})
} else {
return [
{
label: 'default',
value: toHexString32(value),
},
]
}
}
const getStorageSlotHash = (
slot: number,
depth: number,
value: any
): string => {
let keys = []
if (typeof value === 'object' && value !== null) {
keys = getFlattenedKeys(depth, value)
}
if (keys.length === 0) {
return defaultAbiCoder.encode(['uint256'], [slot])
} else {
let slotHash = toHexString32(slot)
for (const key of keys) {
slotHash = keccak256(toHexString32(key) + remove0x(slotHash))
}
return slotHash
}
}
const parseInputSlots = (layout: any, inputTypeName: string): any[] => {
const inputType = layout.types[inputTypeName]
if (inputType.encoding === 'mapping') {
return parseInputSlots(layout, inputType.value)
} else if (inputType.encoding === 'inplace') {
if (inputType.members) {
return inputType.members.map((member: any) => {
return {
label: member.label,
slot: member.slot,
}
})
} else {
return [
{
label: 'default',
slot: 0,
},
]
}
} else {
throw new Error('Encoding type not supported.')
}
}
export const getModifiableStorageFactory = async (
name: string
): Promise<ContractFactory> => {
const contractFactory = await ethers.getContractFactory(name)
const proxyFactory = await ethers.getContractFactory(
'Helper_ModifiableStorage'
)
const originalDeploy = contractFactory.deploy.bind(contractFactory)
contractFactory.deploy = async (...args: any[]): Promise<Contract> => {
const originalDefinePropertyFn = Object.defineProperty
Object.defineProperty = (
object: any,
propName: string,
props: any
): void => {
if (props.writable === false) {
props.writable = true
}
originalDefinePropertyFn(object, propName, props)
}
const contract = await originalDeploy(...args)
const proxy = await proxyFactory.deploy(contract.address)
;(contract as any).address = proxy.address
;(contract as any).resolvedAddress = proxy.address
;(contract as any).__setStorageSlot = proxy.__setStorageSlot.bind(proxy)
;(contract as any).__getStorageSlot = proxy.__getStorageSlot.bind(proxy)
;(contract as any).__setContractStorage = async (value: any) => {
await setContractStorage(
contract,
((await readArtifact(bre.config.paths.artifacts, name)) as any)
.storageLayout,
value
)
}
;(contract as any).__checkContractStorage = async (value: any) => {
await checkContractStorage(
contract,
((await readArtifact(bre.config.paths.artifacts, name)) as any)
.storageLayout,
value
)
}
Object.defineProperty = originalDefinePropertyFn
return contract
}
return contractFactory
}
export const setContractStorage = async (
contract: Contract,
layout: any,
storage: any
): Promise<void> => {
storage = storage || {}
for (const [key, value] of Object.entries(storage)) {
const layoutMap = layout.storage.find((lmap: any) => {
return lmap.label === key
})
const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot, 10)
let depth = (layoutMap.type.match(/t_mapping/g) || []).length
if (typeof value !== 'object') {
const slotHash = getStorageSlotHash(slot, depth, value)
await contract.__setStorageSlot(slotHash, toHexString32(value as string))
} else {
if (key === 'contractStorage' || key === 'verifiedContractStorage') {
for (const [subKey1, subValue1] of Object.entries(value)) {
for (const [subKey, subValue] of Object.entries(subValue1)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey1]: {
[subKey]: subValue,
},
})
const slotValues = getFlattenedValues(depth, {
[subKey1]: {
[subKey]: subValue,
},
})
for (const slotValue of slotValues) {
const slotIndex = inputSlots.find((inputSlot) => {
return inputSlot.label === slotValue.label
}).slot
const slotHash = toHexString32(
BigNumber.from(baseSlotHash).add(slotIndex)
)
await contract.__setStorageSlot(slotHash, slotValue.value)
}
}
}
} else {
for (const [subKey, subValue] of Object.entries(value)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey]: subValue,
})
const slotValues = getFlattenedValues(depth, {
[subKey]: subValue,
})
for (const slotValue of slotValues) {
const slotIndex = inputSlots.find((inputSlot) => {
return inputSlot.label === slotValue.label
}).slot
const slotHash = toHexString32(
BigNumber.from(baseSlotHash).add(slotIndex)
)
await contract.__setStorageSlot(slotHash, slotValue.value)
}
}
}
}
}
}
export const checkContractStorage = async (
contract: Contract,
layout: any,
storage: any
): Promise<void> => {
storage = storage || {}
for (const [key, value] of Object.entries(storage)) {
const layoutMap = layout.storage.find((lmap: any) => {
return lmap.label === key
})
const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot, 10)
const depth = (layoutMap.type.match(/t_mapping/g) || []).length
if (typeof value !== 'object') {
const slotHash = getStorageSlotHash(slot, depth, value)
const retSlotValue = await contract.__getStorageSlot(slotHash)
if (retSlotValue !== toHexString32(value as string)) {
throw new Error(
`Resulting state of ${key} (${retSlotValue}) did not match expected state (${toHexString32(
value as string
)})`
)
}
} else {
if (key === 'contractStorage' || key === 'verifiedContractStorage') {
for (const [subKey1, subValue1] of Object.entries(value)) {
for (const [subKey, subValue] of Object.entries(subValue1)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey1]: {
[subKey]: subValue,
},
})
const slotValues = getFlattenedValues(depth, {
[subKey1]: {
[subKey]: subValue,
},
})
for (const slotValue of slotValues) {
const slotIndex = inputSlots.find((inputSlot) => {
return inputSlot.label === slotValue.label
}).slot
const slotHash = toHexString32(
BigNumber.from(baseSlotHash).add(slotIndex)
)
const retSlotValue = await contract.__getStorageSlot(slotHash)
if (retSlotValue !== slotValue.value) {
throw new Error(
`Resulting state of ${slotValue.label} (${retSlotValue}) did not match expected state (${slotValue.value}).`
)
}
}
}
}
} else {
for (const [subKey, subValue] of Object.entries(value)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey]: subValue,
})
const slotValues = getFlattenedValues(depth, {
[subKey]: subValue,
})
for (const slotValue of slotValues) {
const slotIndex = inputSlots.find((inputSlot) => {
return inputSlot.label === slotValue.label
}).slot
const slotHash = toHexString32(
BigNumber.from(baseSlotHash).add(slotIndex)
)
const retSlotValue = await contract.__getStorageSlot(slotHash)
if (retSlotValue !== slotValue.value) {
throw new Error(
`Resulting state of ${slotValue.label} (${retSlotValue}) did not match expected state (${slotValue.value}).`
)
}
}
}
}
}
}
}
export * from './test-runner'
export * from './test.types'
export * from './json-test-runner'
import { expect } from '../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, BigNumber } from 'ethers'
export const runJsonTest = (contractName: string, json: any): void => {
let contract: Contract
before(async () => {
contract = await (await ethers.getContractFactory(contractName)).deploy()
})
for (const [functionName, functionTests] of Object.entries(json)) {
describe(functionName, () => {
for (const [key, test] of Object.entries(functionTests)) {
it(`should run test: ${key}`, async () => {
if (test.revert) {
await expect(contract.functions[functionName](...test.in)).to.be
.reverted
} else {
expect(
await contract.functions[functionName](...test.in)
).to.deep.equal(
test.out.map((out: any) => {
if (typeof out === 'number') {
return BigNumber.from(out)
} else {
return out
}
})
)
}
})
}
})
}
}
......@@ -3,7 +3,8 @@ import { expect } from '../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, BigNumber, ContractFactory } from 'ethers'
import { cloneDeep } from 'lodash'
import { cloneDeep, merge } from 'lodash'
import { smoddit, smockit, ModifiableContract } from '@eth-optimism/smock'
/* Internal Imports */
import {
......@@ -27,16 +28,19 @@ import {
isTestStep_EXTCODECOPY,
isTestStep_REVERT,
} from './test.types'
import { encodeRevertData } from '../codec'
import { getModifiableStorageFactory } from '../storage/contract-storage'
import { GAS_LIMIT, NON_NULL_BYTES32 } from '../constants'
import { encodeRevertData, REVERT_FLAGS } from '../codec'
import {
OVM_TX_GAS_LIMIT,
RUN_OVM_TEST_GAS,
NON_NULL_BYTES32,
} from '../constants'
export class ExecutionManagerTestRunner {
private snapshot: string
private contracts: {
OVM_SafetyChecker: Contract
OVM_StateManager: Contract
OVM_ExecutionManager: Contract
OVM_StateManager: ModifiableContract
OVM_ExecutionManager: ModifiableContract
Helper_TestRunner: Contract
Factory__Helper_TestRunner_CREATE: ContractFactory
} = {
......@@ -55,14 +59,14 @@ export class ExecutionManagerTestRunner {
test.subTests?.map((subTest) => {
this.run({
...subTest,
preState: {
...test.preState,
...subTest.preState,
},
postState: {
...test.postState,
...subTest.postState,
},
preState: merge(
cloneDeep(test.preState),
cloneDeep(subTest.preState)
),
postState: merge(
cloneDeep(test.postState),
cloneDeep(subTest.postState)
),
})
})
......@@ -78,8 +82,8 @@ export class ExecutionManagerTestRunner {
replacedParameter = this.setPlaceholderStrings(parameter)
})
beforeEach(async () => {
await this.contracts.OVM_StateManager.__setContractStorage({
beforeEach(() => {
this.contracts.OVM_StateManager.smodify.set({
accounts: {
[this.contracts.Helper_TestRunner.address]: {
nonce: 0,
......@@ -90,22 +94,27 @@ export class ExecutionManagerTestRunner {
})
})
beforeEach(async () => {
await this.contracts.OVM_ExecutionManager.__setContractStorage(
beforeEach(() => {
this.contracts.OVM_ExecutionManager.smodify.set(
replacedTest.preState.ExecutionManager
)
await this.contracts.OVM_StateManager.__setContractStorage(
this.contracts.OVM_StateManager.smodify.set(
replacedTest.preState.StateManager
)
})
afterEach(async () => {
await this.contracts.OVM_ExecutionManager.__checkContractStorage(
expect(
await this.contracts.OVM_ExecutionManager.smodify.check(
replacedTest.postState.ExecutionManager
)
await this.contracts.OVM_StateManager.__checkContractStorage(
).to.equal(true)
expect(
await this.contracts.OVM_StateManager.smodify.check(
replacedTest.postState.StateManager
)
).to.equal(true)
})
const itfn = parameter.focus ? it.only : it
......@@ -131,21 +140,54 @@ export class ExecutionManagerTestRunner {
private async initContracts() {
if (this.snapshot) {
await ethers.provider.send('evm_revert', [this.snapshot])
this.snapshot = await ethers.provider.send('evm_snapshot', [])
return
}
this.contracts.OVM_SafetyChecker = await (
const AddressManager = await (
await ethers.getContractFactory('Lib_AddressManager')
).deploy()
const SafetyChecker = await (
await ethers.getContractFactory('OVM_SafetyChecker')
).deploy()
const MockSafetyChecker = smockit(SafetyChecker)
MockSafetyChecker.smocked.isBytecodeSafe.will.return.with(true)
this.contracts.OVM_SafetyChecker = MockSafetyChecker
await AddressManager.setAddress(
'OVM_SafetyChecker',
this.contracts.OVM_SafetyChecker.address
)
this.contracts.OVM_ExecutionManager = await (
await getModifiableStorageFactory('OVM_ExecutionManager')
).deploy(this.contracts.OVM_SafetyChecker.address)
await smoddit('OVM_ExecutionManager')
).deploy(
AddressManager.address,
{
minTransactionGasLimit: 0,
maxTransactionGasLimit: 1_000_000_000,
maxGasPerQueuePerEpoch: 1_000_000_000_000,
secondsPerEpoch: 600,
},
{
ovmCHAINID: 420,
}
)
this.contracts.OVM_StateManager = await (
await getModifiableStorageFactory('OVM_StateManager')
).deploy(this.contracts.OVM_ExecutionManager.address)
await smoddit('OVM_StateManager')
).deploy(await this.contracts.OVM_ExecutionManager.signer.getAddress())
await this.contracts.OVM_StateManager.setExecutionManager(
this.contracts.OVM_ExecutionManager.address
)
this.contracts.Helper_TestRunner = await (
await ethers.getContractFactory('Helper_TestRunner')
).deploy()
this.contracts.Factory__Helper_TestRunner_CREATE = await ethers.getContractFactory(
'Helper_TestRunner_CREATE'
)
......@@ -153,6 +195,10 @@ export class ExecutionManagerTestRunner {
this.snapshot = await ethers.provider.send('evm_snapshot', [])
}
public static getDummyAddress(placeholder: string): string {
return '0x' + (placeholder.split('$DUMMY_OVM_ADDRESS_')[1] + '0').repeat(20)
}
private setPlaceholderStrings(obj: any) {
const getReplacementString = (kv: string): string => {
if (kv === '$OVM_EXECUTION_MANAGER') {
......@@ -164,7 +210,7 @@ export class ExecutionManagerTestRunner {
} else if (kv === '$OVM_CALL_HELPER') {
return this.contracts.Helper_TestRunner.address
} else if (kv.startsWith('$DUMMY_OVM_ADDRESS_')) {
return '0x' + (kv.split('$DUMMY_OVM_ADDRESS_')[1] + '0').repeat(20)
return ExecutionManagerTestRunner.getDummyAddress(kv)
} else {
return kv
}
......@@ -202,8 +248,10 @@ export class ExecutionManagerTestRunner {
const runStep: TestStep_CALL = {
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
target: this.contracts.Helper_TestRunner.address,
gasLimit: OVM_TX_GAS_LIMIT,
target: ExecutionManagerTestRunner.getDummyAddress(
'$DUMMY_OVM_ADDRESS_1'
),
subSteps: step.functionParams.subSteps,
},
expectedReturnStatus: true,
......@@ -215,23 +263,25 @@ export class ExecutionManagerTestRunner {
await this.contracts.OVM_ExecutionManager.run(
{
timestamp: step.functionParams.timestamp,
queueOrigin: step.functionParams.queueOrigin,
number: 0,
l1QueueOrigin: step.functionParams.queueOrigin,
l1Txorigin: step.functionParams.origin,
entrypoint: step.functionParams.entrypoint,
origin: step.functionParams.origin,
msgSender: step.functionParams.msgSender,
gasLimit: step.functionParams.gasLimit,
data: calldata,
},
this.contracts.OVM_StateManager.address
this.contracts.OVM_StateManager.address,
{ gasLimit: RUN_OVM_TEST_GAS }
)
} else {
await this.contracts.OVM_ExecutionManager.ovmCALL(
GAS_LIMIT / 2,
this.contracts.Helper_TestRunner.address,
OVM_TX_GAS_LIMIT,
ExecutionManagerTestRunner.getDummyAddress('$DUMMY_OVM_ADDRESS_1'),
this.contracts.Helper_TestRunner.interface.encodeFunctionData(
'runSingleTestStep',
[this.parseTestStep(step)]
)
),
{ gasLimit: RUN_OVM_TEST_GAS }
)
}
}
......@@ -250,6 +300,16 @@ export class ExecutionManagerTestRunner {
return false
} else if (isTestStep_Context(step)) {
return true
} else if (isTestStep_CALL(step)) {
if (
isRevertFlagError(step.expectedReturnValue) &&
(step.expectedReturnValue.flag === REVERT_FLAGS.INVALID_STATE_ACCESS ||
step.expectedReturnValue.flag === REVERT_FLAGS.STATIC_VIOLATION)
) {
return step.expectedReturnStatus
} else {
return true
}
} else {
return step.expectedReturnStatus
}
......@@ -316,10 +376,6 @@ export class ExecutionManagerTestRunner {
return '0x'
}
if (isTestStep_REVERT(step)) {
return step.expectedReturnValue || '0x'
}
if (isRevertFlagError(step.expectedReturnValue)) {
return encodeRevertData(
step.expectedReturnValue.flag,
......@@ -329,15 +385,27 @@ export class ExecutionManagerTestRunner {
)
}
if (isTestStep_REVERT(step)) {
return step.expectedReturnValue || '0x'
}
let returnData: any[] = []
if (isTestStep_CALL(step)) {
if (step.expectedReturnValue === '0x00') {
return step.expectedReturnValue
} else {
} else if (
typeof step.expectedReturnValue === 'string' ||
step.expectedReturnValue === undefined
) {
returnData = [
step.expectedReturnStatus,
step.expectedReturnValue || '0x',
]
} else {
returnData = [
step.expectedReturnValue.ovmSuccess,
step.expectedReturnValue.returnData,
]
}
} else if (BigNumber.isBigNumber(step.expectedReturnValue)) {
returnData = [step.expectedReturnValue.toHexString()]
......
......@@ -3,8 +3,10 @@ import { BigNumber } from 'ethers'
export type ContextOpcode =
| 'ovmCALLER'
| 'ovmNUMBER'
| 'ovmADDRESS'
| 'ovmORIGIN'
| 'ovmL1TXORIGIN'
| 'ovmL1QUEUEORIGIN'
| 'ovmTIMESTAMP'
| 'ovmGASLIMIT'
| 'ovmCHAINID'
......@@ -32,7 +34,7 @@ interface TestStep_REVERT {
functionName: 'ovmREVERT'
revertData?: string
expectedReturnStatus: boolean
expectedReturnValue?: string
expectedReturnValue?: string | RevertFlagError
}
interface TestStep_EXTCODESIZE {
......@@ -92,7 +94,10 @@ export interface TestStep_CALL {
subSteps?: TestStep[]
}
expectedReturnStatus: boolean
expectedReturnValue?: string | RevertFlagError
expectedReturnValue?:
| string
| RevertFlagError
| { ovmSuccess: boolean; returnData: string }
}
interface TestStep_CREATE {
......@@ -169,11 +174,14 @@ export const isTestStep_Context = (
): step is TestStep_Context => {
return [
'ovmCALLER',
'ovmNUMBER',
'ovmADDRESS',
'ovmORIGIN',
'ovmNUMBER',
'ovmL1TXORIGIN',
'ovmTIMESTAMP',
'ovmGASLIMIT',
'ovmCHAINID',
'ovmL1QUEUEORIGIN',
].includes(step.functionName)
}
......
export * from './trie-test-generator'
/* External Imports */
import * as rlp from 'rlp'
import { default as seedbytes } from 'random-bytes-seed'
import { SecureTrie, BaseTrie } from 'merkle-patricia-tree'
/* Internal Imports */
import { fromHexString, toHexString } from '../utils'
import { NULL_BYTES32 } from '../constants'
export interface TrieNode {
key: string
val: string
}
export interface InclusionProofTest {
key: string
val: string
proof: string
root: string
}
export interface NodeUpdateTest extends InclusionProofTest {
newRoot: string
}
export interface EthereumAccount {
address?: string
nonce: number
balance: number
codeHash: string
storageRoot?: string
storage?: TrieNode[]
}
export interface AccountProofTest {
address: string
account: EthereumAccount
accountTrieWitness: string
accountTrieRoot: string
}
export interface AccountUpdateTest extends AccountProofTest {
newAccountTrieRoot: string
}
const rlpEncodeAccount = (account: EthereumAccount): string => {
return toHexString(
rlp.encode([
account.nonce,
account.balance,
account.storageRoot || NULL_BYTES32,
account.codeHash || NULL_BYTES32,
])
)
}
const rlpDecodeAccount = (encoded: string): EthereumAccount => {
const decoded = rlp.decode(fromHexString(encoded)) as any
return {
nonce: decoded[0].length ? parseInt(decoded[0], 16) : 0,
balance: decoded[1].length ? parseInt(decoded[1], 16) : 0,
storageRoot: decoded[2].length ? toHexString(decoded[2]) : NULL_BYTES32,
codeHash: decoded[3].length ? toHexString(decoded[3]) : NULL_BYTES32,
}
}
const makeTrie = async (
nodes: TrieNode[],
secure?: boolean
): Promise<{
trie: SecureTrie | BaseTrie
TrieClass: any
}> => {
const TrieClass = secure ? SecureTrie : BaseTrie
const trie = new TrieClass()
for (const node of nodes) {
await trie.put(fromHexString(node.key), fromHexString(node.val))
}
return {
trie,
TrieClass,
}
}
export class TrieTestGenerator {
constructor(
public _TrieClass: any,
public _trie: SecureTrie | BaseTrie,
public _nodes: TrieNode[],
public _subGenerators?: TrieTestGenerator[]
) {}
static async fromNodes(opts: {
nodes: TrieNode[]
secure?: boolean
}): Promise<TrieTestGenerator> {
const { trie, TrieClass } = await makeTrie(opts.nodes, opts.secure)
return new TrieTestGenerator(TrieClass, trie, opts.nodes)
}
static async fromRandom(opts: {
seed: string
nodeCount: number
secure?: boolean
keySize?: number
valSize?: number
}): Promise<TrieTestGenerator> {
const getRandomBytes = seedbytes(opts.seed)
const nodes: TrieNode[] = [...Array(opts.nodeCount)].map(() => {
return {
key: toHexString(getRandomBytes(opts.keySize || 32)),
val: toHexString(getRandomBytes(opts.valSize || 32)),
}
})
return TrieTestGenerator.fromNodes({
nodes,
secure: opts.secure,
})
}
static async fromAccounts(opts: {
accounts: EthereumAccount[]
secure?: boolean
}): Promise<TrieTestGenerator> {
const subGenerators: TrieTestGenerator[] = []
for (const account of opts.accounts) {
if (account.storage) {
const subGenerator = await TrieTestGenerator.fromNodes({
nodes: account.storage,
secure: opts.secure,
})
account.storageRoot = toHexString(subGenerator._trie.root)
subGenerators.push(subGenerator)
}
}
const nodes = opts.accounts.map((account) => {
return {
key: account.address,
val: rlpEncodeAccount(account),
}
})
const { trie, TrieClass } = await makeTrie(nodes, opts.secure)
return new TrieTestGenerator(TrieClass, trie, nodes, subGenerators)
}
public async makeInclusionProofTest(
key: string | number
): Promise<InclusionProofTest> {
if (typeof key === 'number') {
key = this._nodes[key].key
}
const trie = this._trie.copy()
const proof = await this.prove(key)
const val = await trie.get(fromHexString(key))
return {
proof: toHexString(rlp.encode(proof)),
key: toHexString(key),
val: toHexString(val),
root: toHexString(trie.root),
}
}
public async makeAllInclusionProofTests(): Promise<InclusionProofTest[]> {
return Promise.all(
this._nodes.map(async (node) => {
return this.makeInclusionProofTest(node.key)
})
)
}
public async makeNodeUpdateTest(
key: string | number,
val: string
): Promise<NodeUpdateTest> {
if (typeof key === 'number') {
key = this._nodes[key].key
}
const trie = this._trie.copy()
const proof = await this.prove(key)
const oldRoot = trie.root
await trie.put(fromHexString(key), fromHexString(val))
const newRoot = trie.root
return {
proof: toHexString(rlp.encode(proof)),
key: toHexString(key),
val: toHexString(val),
root: toHexString(oldRoot),
newRoot: toHexString(newRoot),
}
}
public async makeAccountProofTest(
address: string | number
): Promise<AccountProofTest> {
if (typeof address === 'number') {
address = this._nodes[address].key
}
const trie = this._trie.copy()
const proof = await this.prove(address)
const account = await trie.get(fromHexString(address))
return {
address,
account: rlpDecodeAccount(toHexString(account)),
accountTrieWitness: toHexString(rlp.encode(proof)),
accountTrieRoot: toHexString(trie.root),
}
}
public async makeAccountUpdateTest(
address: string | number,
account: EthereumAccount
): Promise<AccountUpdateTest> {
if (typeof address === 'number') {
address = this._nodes[address].key
}
const trie = this._trie.copy()
const proof = await this.prove(address)
const oldRoot = trie.root
await trie.put(
fromHexString(address),
fromHexString(rlpEncodeAccount(account))
)
const newRoot = trie.root
return {
address,
account,
accountTrieWitness: toHexString(rlp.encode(proof)),
accountTrieRoot: toHexString(oldRoot),
newAccountTrieRoot: toHexString(newRoot),
}
}
private async prove(key: string): Promise<any> {
return this._TrieClass.prove(this._trie, fromHexString(key))
}
}
/* External Imports */
import { BigNumber } from 'ethers'
export interface OVMAccount {
nonce: number
balance: number
nonce: number | BigNumber
balance: number | BigNumber
storageRoot: string
codeHash: string
ethAddress: string
}
export const toOVMAccount = (result: any[]): OVMAccount => {
return {
nonce: result[0].toNumber(),
balance: result[1].toNumber(),
storageRoot: result[2],
codeHash: result[3],
ethAddress: result[4],
}
}
/* External Imports */
import { BigNumber } from 'ethers'
/**
* Converts a string or buffer to a '0x'-prefixed hex string.
* @param buf String or buffer to convert.
* @returns '0x'-prefixed string.
*/
export const toHexString = (buf: Buffer | string): string => {
return '0x' + fromHexString(buf).toString('hex')
}
/**
* Converts a '0x'-prefixed string to a buffer.
* @param str '0x'-prefixed string to convert.
* @returns Hex buffer.
*/
export const fromHexString = (str: string | Buffer): Buffer => {
if (typeof str === 'string' && str.startsWith('0x')) {
return Buffer.from(str.slice(2), 'hex')
}
return Buffer.from(str)
}
export const toHexString32 = (
input: Buffer | string | number,
padRight = false
): string => {
if (typeof input === 'number') {
input = BigNumber.from(input).toHexString()
}
input = toHexString(input).slice(2)
return '0x' + (padRight ? input.padEnd(64, '0') : input.padStart(64, '0'))
}
export const getHexSlice = (
input: Buffer | string,
start: number,
length: number
): string => {
return toHexString(fromHexString(input).slice(start, start + length))
}
/**
* Generates a hex string of repeated bytes.
* @param byte Byte to repeat.
* @param len Number of times to repeat the byte.
* @return '0x'-prefixed hex string filled with the provided byte.
*/
export const makeHexString = (byte: string, len: number): string => {
return '0x' + byte.repeat(len)
}
/**
* Genereates an address with a repeated byte.
* @param byte Byte to repeat in the address.
* @return Address filled with the repeated byte.
*/
export const makeAddress = (byte: string): string => {
return makeHexString(byte, 20)
}
/**
* Removes '0x' from a hex string.
* @param str Hex string to remove '0x' from.
* @returns String without the '0x' prefix.
*/
export const remove0x = (str: string): string => {
if (str.startsWith('0x')) {
return str.slice(2)
......
export const getEthTime = async (provider: any): Promise<number> => {
return (await provider.getBlock('latest')).timestamp
}
export const setEthTime = async (
provider: any,
time: number
): Promise<void> => {
await provider.send('evm_setNextBlockTimestamp', [time])
}
export const increaseEthTime = async (
provider: any,
amount: number
): Promise<void> => {
await setEthTime(provider, (await getEthTime(provider)) + amount)
await provider.send('evm_mine', [])
}
export const getBlockTime = async (
provider: any,
block: number
): Promise<number> => {
await provider.send('evm_mine', [])
return (await provider.getBlock(block)).timestamp
}
export const getNextBlockNumber = async (provider: any): Promise<number> => {
return (await provider.getBlock('latest')).number + 1
}
export * from './buffer-utils'
export * from './byte-utils'
export * from './eth-time'
......@@ -20,8 +20,7 @@
"node_modules/@types"
]
},
"include": ["*.ts", "**/*.ts", "artifacts/*.json"],
"exclude": ["./build", "node_modules"],
"include": ["src/**/*.ts", "artifacts/*.json"],
"files": [
"./buidler.config.ts",
"./buidler-env.d.ts",
......
......@@ -31,7 +31,8 @@
},
"linterOptions": {
"exclude": [
"**/node_modules/**/*"
"**/node_modules/**/*",
"bin/**/*"
]
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
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