Commit 528f57f7 authored by Kelvin Fichter's avatar Kelvin Fichter

Initial commit

parents
node_modules/
artifacts/
cache/
/// <reference types="@nomiclabs/buidler-ethers/src/type-extensions" />
/// <reference types="@nomiclabs/buidler-waffle/src/type-extensions" />
\ No newline at end of file
import { usePlugin, BuidlerConfig } from '@nomiclabs/buidler/config'
import {
DEFAULT_ACCOUNTS_BUIDLER,
GAS_LIMIT,
} from './test/helpers/constants'
usePlugin('@nomiclabs/buidler-ethers')
usePlugin('@nomiclabs/buidler-waffle')
const config: BuidlerConfig = {
networks: {
buidlerevm: {
accounts: DEFAULT_ACCOUNTS_BUIDLER,
blockGasLimit: GAS_LIMIT * 2,
},
},
mocha: {
timeout: 50000,
},
solc: {
version: "0.7.0",
optimizer: { enabled: true, runs: 200 },
},
}
export default config
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_StateManager } from "../../iOVM/execution/iOVM_StateManager.sol";
import { iOVM_DataTypes } from "../../iOVM/codec/iOVM_DataTypes.sol";
contract OVM_StateManager is iOVM_StateManager {
enum ItemState {
ITEM_UNTOUCHED,
ITEM_LOADED,
ITEM_CHANGED
}
mapping (address => iOVM_DataTypes.OVMAccount) public accounts;
mapping (address => iOVM_DataTypes.OVMAccount) public pendingAccounts;
mapping (address => mapping (bytes32 => bytes32)) public contractStorage;
mapping (address => mapping (bytes32 => bool)) public verifiedContractStorage;
mapping (bytes32 => ItemState) public itemStates;
function putAccount(
address _address,
iOVM_DataTypes.OVMAccount memory _account
)
override
public
{
accounts[_address] = _account;
}
function getAccount(address _address)
override
public
returns (
iOVM_DataTypes.OVMAccount memory _account
)
{
return accounts[_address];
}
function hasAccount(
address _address
)
override
public
returns (
bool _exists
)
{
return getAccount(_address).codeHash != bytes32(0);
}
function incrementAccountNonce(
address _address
)
override
public
{
accounts[_address].nonce += 1;
}
function initPendingAccount(
address _address
)
override
public
{
iOVM_DataTypes.OVMAccount storage account = accounts[_address];
account.nonce = 1;
account.codeHash = keccak256(hex'80');
}
function commitPendingAccount(
address _address,
address _ethAddress,
bytes32 _codeHash
)
override
public
{
iOVM_DataTypes.OVMAccount storage account = accounts[_address];
account.ethAddress = _ethAddress;
account.codeHash = _codeHash;
}
function putContractStorage(
address _contract,
bytes32 _key,
bytes32 _value
)
override
public
{
contractStorage[_contract][_key] = _value;
verifiedContractStorage[_contract][_key] = true;
}
function getContractStorage(
address _contract,
bytes32 _key
)
override
public
returns (
bytes32 _value
)
{
return contractStorage[_contract][_key];
}
function hasContractStorage(
address _contract,
bytes32 _key
)
override
public
returns (
bool _exists
)
{
return verifiedContractStorage[_contract][_key];
}
function testAndSetAccountLoaded(
address _address
)
override
public
returns (
bool _wasAccountAlreadyLoaded
)
{
return _testItemState(
keccak256(abi.encodePacked(_address)),
ItemState.ITEM_LOADED
);
}
function testAndSetAccountChanged(
address _address
)
override
public
returns (
bool _wasAccountAlreadyChanged
)
{
return _testItemState(
keccak256(abi.encodePacked(_address)),
ItemState.ITEM_CHANGED
);
}
function testAndSetContractStorageLoaded(
address _contract,
bytes32 _key
)
override
public
returns (
bool _wasContractStorageAlreadyLoaded
)
{
return _testItemState(
keccak256(abi.encodePacked(_contract, _key)),
ItemState.ITEM_LOADED
);
}
function testAndSetContractStorageChanged(
address _contract,
bytes32 _key
)
override
public
returns (
bool _wasContractStorageAlreadyChanged
)
{
return _testItemState(
keccak256(abi.encodePacked(_contract, _key)),
ItemState.ITEM_CHANGED
);
}
/*
* Internal Functions
*/
function _testItemState(
bytes32 _item,
ItemState _minItemState
)
internal
returns (
bool _wasItemState
)
{
ItemState itemState = itemStates[_item];
bool wasItemState = itemState >= _minItemState;
if (wasItemState == false) {
itemStates[_item] = _minItemState;
}
return wasItemState;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
interface iOVM_DataTypes {
struct OVMAccount {
uint256 nonce;
uint256 balance;
bytes32 storageRoot;
bytes32 codeHash;
address ethAddress;
}
struct EVMAccount {
uint256 nonce;
uint256 balance;
bytes32 storageRoot;
bytes32 codeHash;
}
struct OVMChainBatchHeader {
uint256 batchIndex;
bytes32 batchRoot;
uint256 batchSize;
uint256 prevTotalElements;
bytes extraData;
}
struct OVMChainInclusionProof {
uint256 index;
bytes32[] siblings;
}
struct OVMTransactionData {
uint256 timestamp;
uint256 queueOrigin;
address entrypoint;
address origin;
address msgSender;
uint256 gasLimit;
bytes data;
}
struct OVMProofMatrix {
bool checkNonce;
bool checkBalance;
bool checkStorageRoot;
bool checkCodeHash;
}
struct OVMQueueElement {
uint256 timestamp;
bytes32 batchRoot;
bool isL1ToL2Batch;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_DataTypes } from "../codec/iOVM_DataTypes.sol";
interface iOVM_ExecutionManager {
enum RevertFlag {
DID_NOT_REVERT,
OUT_OF_GAS,
INTENTIONAL_REVERT,
EXCEEDS_NUISANCE_GAS,
INVALID_STATE_ACCESS,
UNSAFE_BYTECODE
}
struct GlobalContext {
uint256 ovmCHAINID;
}
struct TransactionContext {
address ovmORIGIN;
uint256 ovmTIMESTAMP;
uint256 ovmGASLIMIT;
uint256 ovmTXGASLIMIT;
uint256 ovmQUEUEORIGIN;
}
struct TransactionRecord {
uint256 ovmGasRefund;
}
struct MessageContext {
address ovmCALLER;
address ovmADDRESS;
bool isStatic;
}
struct MessageRecord {
uint256 nuisanceGasLeft;
RevertFlag revertFlag;
}
function run(
iOVM_DataTypes.OVMTransactionData calldata _transaction,
address _txStateManager
) external;
/*******************
* Context Opcodes *
*******************/
function ovmCALLER() external returns (address _caller);
function ovmADDRESS() external returns (address _address);
function ovmORIGIN() external returns (address _origin);
function ovmTIMESTAMP() external returns (uint256 _timestamp);
function ovmGASLIMIT() external returns (uint256 _gasLimit);
function ovmCHAINID() external returns (uint256 _chainId);
/*******************
* Halting Opcodes *
*******************/
function ovmREVERT(bytes memory _data) external;
/*****************************
* Contract Creation Opcodes *
*****************************/
function ovmCREATE(bytes memory _bytecode) external returns (address _contract);
function ovmCREATE2(bytes memory _bytecode, bytes32 _salt) external returns (address _contract);
function safeCREATE(address _address, bytes memory _bytecode) external;
/****************************
* Contract Calling Opcodes *
****************************/
function ovmCALL(uint256 _gasLimit, address _address, bytes memory _calldata) external returns (bool _success, bytes memory _returndata);
function ovmSTATICCALL(uint256 _gasLimit, address _address, bytes memory _calldata) external returns (bool _success, bytes memory _returndata);
function ovmDELEGATECALL(uint256 _gasLimit, address _address, bytes memory _calldata) external returns (bool _success, bytes memory _returndata);
/****************************
* Contract Storage Opcodes *
****************************/
function ovmSLOAD(bytes32 _key) external returns (bytes32 _value);
function ovmSSTORE(bytes32 _key, bytes32 _value) external;
/*************************
* Contract Code Opcodes *
*************************/
function ovmEXTCODECOPY(address _contract, uint256 _offset, uint256 _length) external returns (bytes memory _code);
function ovmEXTCODESIZE(address _contract) external returns (uint256 _size);
function ovmEXTCODEHASH(address _contract) external returns (bytes32 _hash);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
interface iOVM_SafetyChecker {
function isBytecodeSafe(bytes memory _bytecode) external view returns (bool _safe);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_DataTypes } from "../codec/iOVM_DataTypes.sol";
interface iOVM_StateManager {
function putAccount(address _address, iOVM_DataTypes.OVMAccount memory _account) external;
function getAccount(address _address) external returns (iOVM_DataTypes.OVMAccount memory _account);
function hasAccount(address _address) external returns (bool _exists);
function incrementAccountNonce(address _address) external;
function initPendingAccount(address _address) external;
function commitPendingAccount(address _address, address _ethAddress, bytes32 _codeHash) external;
function putContractStorage(address _contract, bytes32 _key, bytes32 _value) external;
function getContractStorage(address _contract, bytes32 _key) external returns (bytes32 _value);
function hasContractStorage(address _contract, bytes32 _key) external returns (bool _exists);
function testAndSetAccountLoaded(address _address) external returns (bool _wasAccountAlreadyLoaded);
function testAndSetAccountChanged(address _address) external returns (bool _wasAccountAlreadyChanged);
function testAndSetContractStorageLoaded(address _contract, bytes32 _key) external returns (bool _wasContractStorageAlreadyLoaded);
function testAndSetContractStorageChanged(address _contract, bytes32 _key) external returns (bool _wasContractStorageAlreadyChanged);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title RLPReader
* @author Hamdi Allam hamdi.allam97@gmail.com
*/
library Lib_RLPReader {
/*
* Data Structures
*/
struct RLPItem {
uint len;
uint memPtr;
}
/*
* Contract Constants
*/
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;
/*
* Public Functions
*/
/**
* @param item RLP encoded bytes
*/
function toRlpItem(
bytes memory item
)
internal
pure
returns (RLPItem memory)
{
uint memPtr;
assembly {
memPtr := add(item, 0x20)
}
return RLPItem(item.length, memPtr);
}
/**
* @param item RLP encoded bytes
*/
function rlpLen(
RLPItem memory item
)
internal
pure
returns (uint)
{
return item.len;
}
/**
* @param item RLP encoded bytes
*/
function payloadLen(
RLPItem memory item
)
internal
pure
returns (uint)
{
return item.len - _payloadOffset(item.memPtr);
}
/**
* @param item RLP encoded list in bytes
*/
function toList(
RLPItem memory item
)
internal
pure
returns (RLPItem[] memory result)
{
require(isList(item));
uint items = numItems(item);
result = new RLPItem[](items);
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 indicator whether encoded payload is a list. negate this function call for isData.
function isList(
RLPItem memory item
)
internal
pure
returns (bool)
{
if (item.len == 0) return false;
uint8 byte0;
uint memPtr = item.memPtr;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < LIST_SHORT_START)
return false;
return true;
}
/** RLPItem conversions into data types **/
// @returns raw rlp encoding in bytes
function toRlpBytes(
RLPItem memory item
)
internal
pure
returns (bytes memory)
{
bytes memory result = new bytes(item.len);
if (result.length == 0) return result;
uint ptr;
assembly {
ptr := add(0x20, result)
}
copy(item.memPtr, ptr, item.len);
return result;
}
// any non-zero byte is considered true
function toBoolean(
RLPItem memory item
)
internal
pure
returns (bool)
{
require(item.len == 1);
uint result;
uint memPtr = item.memPtr;
assembly {
result := byte(0, mload(memPtr))
}
return result == 0 ? false : true;
}
function toAddress(
RLPItem memory item
)
internal
pure
returns (address)
{
// 1 byte for the length prefix
require(item.len == 21);
return address(toUint(item));
}
function toUint(
RLPItem memory item
)
internal
pure
returns (uint)
{
require(item.len > 0 && item.len <= 33);
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset;
uint result;
uint memPtr = item.memPtr + offset;
assembly {
result := mload(memPtr)
// shfit to the correct location if neccesary
if lt(len, 32) {
result := div(result, exp(256, sub(32, len)))
}
}
return result;
}
// enforces 32 byte length
function toUintStrict(
RLPItem memory item
)
internal
pure
returns (uint)
{
// one byte prefix
require(item.len == 33);
uint result;
uint memPtr = item.memPtr + 1;
assembly {
result := mload(memPtr)
}
return result;
}
function toBytes(
RLPItem memory item
)
internal
pure
returns (bytes memory)
{
require(item.len > 0);
uint offset = _payloadOffset(item.memPtr);
uint len = item.len - offset; // data length
bytes memory result = new bytes(len);
uint destPtr;
assembly {
destPtr := add(0x20, result)
}
copy(item.memPtr + offset, destPtr, len);
return result;
}
/*
* Private Functions
*/
// @return number of payload items inside an encoded list.
function numItems(
RLPItem memory item
)
private
pure
returns (uint)
{
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 count;
}
// @return entire rlp item byte length
function _itemLength(
uint memPtr
)
private
pure
returns (uint len)
{
uint byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START)
return 1;
else if (byte0 < STRING_LONG_START)
return byte0 - STRING_SHORT_START + 1;
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
/* 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))
}
}
else if (byte0 < LIST_LONG_START) {
return byte0 - LIST_SHORT_START + 1;
}
else {
assembly {
let byteLen := sub(byte0, 0xf7)
memPtr := add(memPtr, 1)
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
len := add(dataLen, add(byteLen, 1))
}
}
}
// @return number of bytes until the data
function _payloadOffset(
uint memPtr
)
private
pure
returns (uint)
{
uint byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
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;
}
/*
* @param src Pointer to source
* @param dest Pointer to destination
* @param len Amount of memory to copy from the source
*/
function copy(
uint src,
uint dest,
uint len
)
private
pure
{
if (len == 0) return;
// copy as many word sizes as possible
for (; len >= WORD_SIZE; len -= WORD_SIZE) {
assembly {
mstore(dest, mload(src))
}
src += WORD_SIZE;
dest += WORD_SIZE;
}
// left over bytes. Mask is used to remove unwanted bytes from the word
uint mask = 256 ** (WORD_SIZE - len) - 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))
}
}
}
\ No newline at end of file
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* 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
*/
library Lib_RLPWriter {
/*
* Public Functions
*/
/**
* RLP encodes a byte string.
* @param self The byte string to encode.
* @return The RLP encoded string in bytes.
*/
function encodeBytes(
bytes memory self
)
internal
pure
returns (bytes memory)
{
bytes memory encoded;
if (self.length == 1 && uint8(self[0]) < 128) {
encoded = self;
} else {
encoded = concat(encodeLength(self.length, 128), self);
}
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.
*/
function encodeList(
bytes[] memory self
)
internal
pure
returns (bytes memory)
{
bytes memory list = flatten(self);
return concat(encodeLength(list.length, 192), list);
}
/**
* RLP encodes a string.
* @param self The string to encode.
* @return The RLP encoded string in bytes.
*/
function encodeString(
string memory self
)
internal
pure
returns (bytes memory)
{
return encodeBytes(bytes(self));
}
/**
* RLP encodes an address.
* @param self The address to encode.
* @return The RLP encoded address in bytes.
*/
function encodeAddress(
address self
)
internal
pure
returns (bytes memory)
{
bytes memory inputBytes;
assembly {
let m := mload(0x40)
mstore(add(m, 20), xor(0x140000000000000000000000000000000000000000, self))
mstore(0x40, add(m, 52))
inputBytes := m
}
return encodeBytes(inputBytes);
}
/**
* RLP encodes a uint.
* @param self The uint to encode.
* @return The RLP encoded uint in bytes.
*/
function encodeUint(
uint self
)
internal
pure
returns (bytes memory)
{
return encodeBytes(toBinary(self));
}
/**
* RLP encodes an int.
* @param self The int to encode.
* @return The RLP encoded int in bytes.
*/
function encodeInt(
int self
)
internal
pure
returns (bytes memory)
{
return encodeUint(uint(self));
}
/**
* RLP encodes a bool.
* @param self The bool to encode.
* @return The RLP encoded bool in bytes.
*/
function encodeBool(
bool self
)
internal
pure
returns (bytes memory)
{
bytes memory encoded = new bytes(1);
encoded[0] = (self ? bytes1(0x01) : bytes1(0x80));
return encoded;
}
/*
* 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.
*/
function encodeLength(
uint len,
uint offset
)
private
pure
returns (bytes memory)
{
bytes memory encoded;
if (len < 56) {
encoded = new bytes(1);
encoded[0] = byte(uint8(len) + uint8(offset));
} else {
uint lenLen;
uint i = 1;
while (len / i != 0) {
lenLen++;
i *= 256;
}
encoded = new bytes(lenLen + 1);
encoded[0] = byte(uint8(lenLen) + uint8(offset) + 55);
for(i = 1; i <= lenLen; i++) {
encoded[i] = byte(uint8((len / (256**(lenLen-i))) % 256));
}
}
return encoded;
}
/**
* 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.
*/
function toBinary(
uint _x
)
private
pure
returns (bytes memory)
{
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;
}
/**
* Copies a piece of memory to another location.
* @notice From: https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol.
* @param _dest Destination location.
* @param _src Source location.
* @param _len Length of memory to copy.
*/
function memcpy(
uint _dest,
uint _src,
uint _len
)
private
pure
{
uint dest = _dest;
uint src = _src;
uint len = _len;
for(; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
uint mask = 256 ** (32 - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
/**
* 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.
*/
function flatten(
bytes[] memory _list
)
private
pure
returns (bytes memory)
{
if (_list.length == 0) {
return new bytes(0);
}
uint len;
uint i = 0;
for (; i < _list.length; i++) {
len += _list[i].length;
}
bytes memory flattened = new bytes(len);
uint flattenedPtr;
assembly { flattenedPtr := add(flattened, 0x20) }
for(i = 0; i < _list.length; i++) {
bytes memory item = _list[i];
uint listPtr;
assembly { listPtr := add(item, 0x20)}
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;
}
}
\ No newline at end of file
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol";
library Lib_EthUtils {
function getCode(
address _address,
uint256 _offset,
uint256 _length
)
internal
view
returns (
bytes memory _code
)
{
assembly {
_code := mload(0x40)
mstore(0x40, add(_code, add(_length, 0x20)))
mstore(_code, _length)
extcodecopy(_address, add(_code, 0x20), _offset, _length)
}
return _code;
}
function getCode(
address _address
)
internal
view
returns (
bytes memory _code
)
{
return getCode(
_address,
0,
getCodeSize(_address)
);
}
function getCodeSize(
address _address
)
internal
view
returns (
uint256 _codeSize
)
{
assembly {
_codeSize := extcodesize(_address)
}
return _codeSize;
}
function getCodeHash(
address _address
)
internal
view
returns (
bytes32 _codeHash
)
{
assembly {
_codeHash := extcodehash(_address)
}
return _codeHash;
}
function createContract(
bytes memory _code
)
internal
returns (
address _created
)
{
assembly {
_created := create(
0,
add(_code, 0x20),
mload(_code)
)
}
return _created;
}
function getAddressForCREATE(
address _creator,
uint256 _nonce
)
internal
view
returns (
address _address
)
{
bytes[] memory encoded = new bytes[](2);
encoded[0] = Lib_RLPWriter.encodeAddress(_creator);
encoded[1] = Lib_RLPWriter.encodeUint(_nonce);
bytes memory encodedList = Lib_RLPWriter.encodeList(encoded);
return getAddressFromHash(keccak256(encodedList));
}
function getAddressForCREATE2(
address _creator,
bytes memory _bytecode,
bytes32 _salt
)
internal
view
returns (address _address)
{
bytes32 hashedData = keccak256(abi.encodePacked(
byte(0xff),
_creator,
_salt,
keccak256(_bytecode)
));
return getAddressFromHash(hashedData);
}
/**
* Determines an address from a 32 byte hash. Since addresses are only
* 20 bytes, we need to retrieve the last 20 bytes from the original
* hash. Converting to uint256 and then uint160 gives us these bytes.
* @param _hash Hash to convert to an address.
* @return Hash converted to an address.
*/
function getAddressFromHash(
bytes32 _hash
)
private
pure
returns (address)
{
return address(bytes20(uint160(uint256(_hash))));
}
}
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
// note: this pattern breaks if an action leads to reversion, since the result will not be stored.
// Thus, only the final action in deployedActions can be an ovmREVERT, and the result must be checked via the callee.
contract MockOvmCodeContract {
bytes[] public EMReturnValuesInConstructor;
bytes[][] public EMReturnValuesInDeployed;
bytes[][] public callDatasForEMInDeployed;
bytes[] public returnDataForCodeContractInDeployed;
uint public callIndex = 0;
bytes public constrRet0;
bytes public constrRet1;
bytes public constCall0;
constructor(bytes[] memory _callsToEMInConstructor, bytes[][] memory _calldatasToEMInDeployed, bytes[] memory _returnDataForCodeContractInDeployed) {
require(_calldatasToEMInDeployed.length == _returnDataForCodeContractInDeployed.length, "Invalid behavior requested for mock code contract: mismatch between number of calldata batches and returndata for post-deployment behavior.");
callDatasForEMInDeployed = _calldatasToEMInDeployed;
returnDataForCodeContractInDeployed = _returnDataForCodeContractInDeployed;
bytes[] memory callsToDoNow = _callsToEMInConstructor;
bytes[] memory returnVals = doEMCalls(callsToDoNow);
constCall0 = callsToDoNow[0];
constrRet0 = returnVals[0];
constrRet1 = returnVals[1];
for (uint i = 0; i < returnVals.length; i++) {
EMReturnValuesInConstructor.push(returnVals[i]);
}
}
fallback() external {
bytes[] memory calldatas = callDatasForEMInDeployed[callIndex];
bytes[] memory returndatas = doEMCalls(calldatas);
EMReturnValuesInDeployed.push();
for (uint i = 0; i < returndatas.length; i++) {
EMReturnValuesInDeployed[callIndex].push(returndatas[i]);
}
bytes memory dataToReturn = returnDataForCodeContractInDeployed[callIndex];
callIndex++;
uint returnLength = dataToReturn.length;
assembly {
return(add(dataToReturn, 0x20), returnLength)
}
}
function doEMCalls(bytes[] memory _calldatas) internal returns(bytes[] memory) {
bytes[] memory calldatas = _calldatas;
bytes[] memory results = new bytes[](calldatas.length);
for (uint i = 0; i < calldatas.length; i++) {
bytes memory data = calldatas[i];
bytes memory result = callExecutionManager(data);
results[i] = result;
}
return results;
}
function callExecutionManager (bytes memory _data) internal returns (bytes memory actionResult) {
uint dataLength = _data.length;
uint returnedLength;
assembly {
function isContextCREATE() -> isCREATE {
isCREATE := iszero(extcodesize(address()))
}
// Note: this function is the only way that the opcodes REVERT, CALLER, EXTCODESIZE, ADDRESS can appear in a code contract which passes SafetyChecker.isBytecodeSafe().
// The static analysis enforces that the EXACT functionality below is implemented by comparing to a reference bytestring.
function doSafeExecutionManagerCall(argOff, argLen, retOffset, retLen) {
let success := call(gas(), caller(), 0, argOff, argLen, retOffset, retLen)
if iszero(success) {
mstore(0, 0x2a2a7adb00000000000000000000000000000000000000000000000000000000) // ovmREVERT(bytes) methodId
returndatacopy(4, 0, 32)
let secondsuccess := call(gas(), caller(), 0, 0, 36, 0, 0)
if iszero(secondsuccess) {
returndatacopy(0, 0, 32)
revert(0, 32)
}
// ovmREVERT will only succeed if we are in a CREATE context, in which case we abort by deploying a single byte contract.
mstore(0,0)
return(0, 1)
}
}
doSafeExecutionManagerCall(add(_data, 0x20), dataLength, 0, 0)
returnedLength := returndatasize()
}
bytes memory returned = new bytes(returnedLength);
assembly {
returndatacopy(add(returned, 0x20), 0, returndatasize())
}
return returned;
}
}
\ 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)
- ovmCALL
- -> ovmCALLER
- -> ovmADDRESS
- -> SLOAD
- -> SSTORE
- -> CREATE/2
- ovmSTATICCALL
- -> ovmCALLER
- -> ovmADDRESS
- -> SLOAD
- -> SSTORE (fail)
- -> CREATE/2 (fail)
- -> ovmCALL -> ovmSSTORE
- -> ovmCALL -> ovmCREATE
- -> ovmSTATICCALL -> RETURN -> SLOAD (fails)
- ovmDELEGATECALL
- -> ovmCALLER
- -> ovmADDRESS
- -> SLOAD
- -> SSTORE
- -> CREATE/2
- -> ovmDELEGATECALL -> ovmCALLER
- -> ovmDELEGATECALL -> ovmADDRESS
- Code-related
- CREATE-types
- do we just duplicate these exactly for CREATE and CREATE2? Probably
- ovmCREATE -> success -> ovmEXTCODE{SIZE,HASH,COPY}
- 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)
- ovmCREATE -> ovmCALL(in constructor) -> ovmSSTORE, return -> ovmREVERT (deployment fails, storage not modified, but state access gas correctly increased)
- 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
- 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)
- 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
- CREATE -> CREATE, ISA
- CREATE -> CREATE -> CREATE ISA
- CREATE -> CALL, ISA
- 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",
"license": "MIT",
"scripts": {
"build": "yarn run build:contracts",
"build:contracts": "buidler compile",
"test": "yarn run test:contracts",
"test:contracts": "buidler test \"test/contracts/OVM/execution/OVM_ExecutionManager/opcodes/OVM_ExecutionManager.opcodes.calling.spec.ts\""
},
"devDependencies": {
"@nomiclabs/buidler": "^1.4.4",
"@nomiclabs/buidler-ethers": "^2.0.0",
"@nomiclabs/buidler-waffle": "^2.0.0",
"@types/chai": "^4.2.12",
"@types/mocha": "^8.0.3",
"@types/node": "^14.6.0",
"assert": "^2.0.0",
"chai": "^4.2.0",
"ethereum-waffle": "3.0.0",
"ethers": "5.0.0",
"mocha": "^8.1.1",
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
}
}
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)
}
export const makeHexString = (byte: string, len: number): string => {
return '0x' + byte.repeat(len)
}
export const makeAddress = (byte: string): string => {
return makeHexString(byte, 20)
}
import { ethers } from '@nomiclabs/buidler'
export const encodeRevertData = (
flag: number,
data: string = '0x',
nuisanceGasLeft: number = 0,
ovmGasRefund: number = 0
): string => {
return ethers.utils.defaultAbiCoder.encode(
['uint256','uint256','uint256','bytes'],
[flag, nuisanceGasLeft, ovmGasRefund, data]
)
}
export const REVERT_FLAGS = {
DID_NOT_REVERT: 0,
OUT_OF_GAS: 1,
INTENTIONAL_REVERT: 2,
EXCEEDS_NUISANCE_GAS: 3,
INVALID_STATE_ACCESS: 4,
UNSAFE_BYTECODE: 5
}
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'
/* Internal Imports */
import { makeHexString, makeAddress } from './byte-utils'
export const DEFAULT_ACCOUNTS = defaultAccounts
export const DEFAULT_ACCOUNTS_BUIDLER = defaultAccounts.map((account) => {
return {
balance: ethers.BigNumber.from(account.balance).toHexString(),
privateKey: account.secretKey,
}
})
export const GAS_LIMIT = 1_000_000_000
export const NULL_BYTES32 = makeHexString('00', 32)
export const NON_NULL_BYTES32 = makeHexString('11', 32)
export const ZERO_ADDRESS = makeAddress('00')
export const NON_ZERO_ADDRESS = makeAddress('11')
/* Internal Imports */
import { DUMMY_BYTES32 } from './bytes32'
import { ZERO_ADDRESS, NON_ZERO_ADDRESS } from '../constants'
import { makeAddress } from '../byte-utils'
import { OVMAccount } from '../types/ovm-types'
export const DUMMY_ACCOUNTS: Array<{
address: string,
data: OVMAccount
}> = [
{
address: makeAddress('12'),
data: {
nonce: 123,
balance: 456,
storageRoot: DUMMY_BYTES32[0],
codeHash: DUMMY_BYTES32[1],
ethAddress: ZERO_ADDRESS,
},
},
{
address: makeAddress('21'),
data: {
nonce: 321,
balance: 654,
storageRoot: DUMMY_BYTES32[2],
codeHash: DUMMY_BYTES32[3],
ethAddress: NON_ZERO_ADDRESS,
},
}
]
/* External Imports */
import { ethers } from 'ethers'
export const DUMMY_BYTES32: string[] = Array.from(
{
length: 10,
},
(_, i) => {
return ethers.utils.keccak256(`0x0${i}`)
}
)
/* Internal Imports */
import { NON_ZERO_ADDRESS } from '../constants'
export const DUMMY_CONTEXT = {
GLOBAL: {
ovmCHAINID: 11
},
TRANSACTION: {
ovmORIGIN: NON_ZERO_ADDRESS,
ovmTIMESTAMP: 22,
ovmGASLIMIT: 33,
ovmTXGASLIMIT: 44,
ovmQUEUEORIGIN: 55,
},
MESSAGE: {
ovmCALLER: NON_ZERO_ADDRESS,
ovmADDRESS: NON_ZERO_ADDRESS,
ovmSTATICCTX: true
}
}
export * from './accounts'
export * from './bytes32'
export * from './context'
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 './codec'
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
let 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)
const modifiedRunTx = async (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]
const modifiedBeforeMessageHandler = async (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]
const modifiedAfterMessageHandler = async (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 sighash = mock.interface.getSighash(functionName)
return calls[sighash] || []
}
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 MockContract = (await MockContractFactory.deploy()) as MockContract
Object.defineProperty = originalDefinePropertyFn
bindMockWatcherToVM()
bindMockContractToVM(MockContract, fns, spec)
return MockContract
}
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
const getLibraryConfig = (ProxyManager: Contract): any => {
return [
{
name: 'Lib_ByteUtils',
params: []
},
{
name: 'Lib_EthUtils',
params: [ProxyManager.address]
},
{
name: 'Lib_RLPReader',
params: []
},
{
name: 'Lib_RLPWriter',
params: []
}
]
}
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> => {
const Factory__Proxy_Manager = await ethers.getContractFactory(
'Proxy_Manager'
)
const Proxy_Manager = await Factory__Proxy_Manager.deploy()
const libraryConfig = getLibraryConfig(Proxy_Manager)
await makeProxies(
Proxy_Manager,
libraryConfig.map((config) => {
return config.name
})
)
for (const config of libraryConfig) {
const Factory__Lib_Contract = await ethers.getContractFactory(
config.name
)
const Lib_Contract = await Factory__Lib_Contract.deploy(
...config.params
)
await Proxy_Manager.setTarget(
config.name,
Lib_Contract.address
)
}
return Proxy_Manager
}
export interface OVMAccount {
nonce: number
balance: number
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 chai = require('chai')
import { solidity } from 'ethereum-waffle'
chai.use(solidity)
const should = chai.should()
const expect = chai.expect
export { should, expect }
{
"compilerOptions": {
"outDir": "./build",
"baseUrl": "./",
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": ["*.ts", "**/*.ts", "artifacts/*.json"],
"exclude": ["./build", "node_modules"],
"files": [
"./buidler.config.ts",
"./buidler-env.d.ts",
"./node_modules/@nomiclabs/buidler-ethers/src/type-extensions.d.ts",
"./node_modules/@nomiclabs/buidler-waffle/src/type-extensions.d.ts"
]
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
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