Commit f06c8652 authored by Kevin Ho's avatar Kevin Ho Committed by GitHub

EOA and Decompressor (#33)

* decompression (not working)

* start EIP155 serialization

* working decompressor

* working CreateEOA, working native EIP155tx

* all three tx types working

* remove only

* fix ECDSAUtil tests

* restore original version of smock

* add proxys

* clean up tests

* clean up decompressor tests

* clean up ProxyDecompressor

* clean up decompressor tests

* add CREATEEOA, SETNONCE, GETNONCE tests

* working ECDSAContractAccount

* fix decompressor

* renaming, add deploy

* add proxyeoa test, cleanup

* remove createEOA type, add extcodesize check

* fix decompressor test

* support create txs

* Decompressor -> Entrypoint

* use safeREVERT instead of requires

* add isAuthenticated state manager test

* update smock

* fix sequencerEntrypoint test

* add isAuthenticated

* Revert "update smock"

This reverts commit 286a222414accf9387dcd12ecbed0a66be586bc8.

* fix test

* Updates to EM

* removed kall from contract account

* Fixed SSLOAD and SSTORE problems

* Fix some broken tests

* Fix more bugs & rename to ProxyEOA

* WIP fix call test

* fix all tests

* add gas check

* update tests

* lint and remove 2000 gas check

* Add fees to default EOA (#56)
Co-authored-by: default avatarKelvin Fichter <kelvinfichter@gmail.com>
Co-authored-by: default avatarKarl Floersch <karl@karlfloersch.com>
parent 21b3e5ad
...@@ -15,6 +15,8 @@ import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_Sa ...@@ -15,6 +15,8 @@ import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_Sa
*/ */
contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
address constant ETH_ERC20_ADDRESS = 0x4200000000000000000000000000000000000006;
/******************** /********************
* Public Functions * * Public Functions *
********************/ ********************/
...@@ -44,35 +46,48 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -44,35 +46,48 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
) )
{ {
address ovmExecutionManager = msg.sender; address ovmExecutionManager = msg.sender;
bool isEthSign = _signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE;
// Address of this contract within the ovm (ovmADDRESS) should be the same as the // 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 // recovered address of the user who signed this message. This is how we manage to shim
// account abstraction even though the user isn't a contract. // account abstraction even though the user isn't a contract.
require( // Need to make sure that the transaction nonce is right and bump it if so.
Lib_SafeExecutionManagerWrapper.safeREQUIRE(
msg.sender,
Lib_ECDSAUtils.recover( Lib_ECDSAUtils.recover(
_transaction, _transaction,
_signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE, isEthSign,
_v, _v,
_r, _r,
_s, _s
Lib_SafeExecutionManagerWrapper.safeCHAINID(ovmExecutionManager)
) == Lib_SafeExecutionManagerWrapper.safeADDRESS(ovmExecutionManager), ) == Lib_SafeExecutionManagerWrapper.safeADDRESS(ovmExecutionManager),
"Signature provided for EOA transaction execution is invalid." "Signature provided for EOA transaction execution is invalid."
); );
Lib_OVMCodec.EOATransaction memory decodedTx = Lib_OVMCodec.decodeEOATransaction(_transaction); Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction(_transaction, isEthSign);
// Need to make sure that the transaction nonce is right and bump it if so. // Need to make sure that the transaction nonce is right.
require( Lib_SafeExecutionManagerWrapper.safeREQUIRE(
decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager) + 1, msg.sender,
decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager),
"Transaction nonce does not match the expected nonce." "Transaction nonce does not match the expected nonce."
); );
// Transfer fee to relayer.
address relayer = Lib_SafeExecutionManagerWrapper.safeCALLER(ovmExecutionManager);
uint256 fee = decodedTx.gasLimit * decodedTx.gasPrice;
Lib_SafeExecutionManagerWrapper.safeCALL(
ovmExecutionManager,
gasleft(),
ETH_ERC20_ADDRESS,
abi.encodeWithSignature("transfer(address,uint256)", relayer, fee)
);
// Contract creations are signalled by sending a transaction to the zero address. // Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.target == address(0)) { if (decodedTx.to == address(0)) {
address created = Lib_SafeExecutionManagerWrapper.safeCREATE( address created = Lib_SafeExecutionManagerWrapper.safeCREATE(
ovmExecutionManager, ovmExecutionManager,
decodedTx.gasLimit, decodedTx.gasLimit - 2000,
decodedTx.data decodedTx.data
); );
...@@ -83,12 +98,12 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -83,12 +98,12 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// We only want to bump the nonce for `ovmCALL` because `ovmCREATE` automatically bumps // 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 // 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. // cases, but since this is a contract we'd end up bumping the nonce twice.
Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, decodedTx.nonce); Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, decodedTx.nonce + 1);
return Lib_SafeExecutionManagerWrapper.safeCALL( return Lib_SafeExecutionManagerWrapper.safeCALL(
ovmExecutionManager, ovmExecutionManager,
decodedTx.gasLimit, decodedTx.gasLimit,
decodedTx.target, decodedTx.to,
decodedTx.data decodedTx.data
); );
} }
......
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_BytesUtils } from "../../libraries/utils/Lib_BytesUtils.sol";
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_ProxyEOA
*/
contract OVM_ProxyEOA {
/***************
* Constructor *
***************/
constructor(
address _implementation
) {
_setImplementation(_implementation);
}
/*********************
* Fallback Function *
*********************/
fallback()
external
{
(bool success, bytes memory returndata) = Lib_SafeExecutionManagerWrapper.safeDELEGATECALL(
msg.sender,
gasleft(),
getImplementation(),
msg.data
);
if (success) {
assembly {
return(add(returndata, 0x20), mload(returndata))
}
} else {
Lib_SafeExecutionManagerWrapper.safeREVERT(
msg.sender,
string(returndata)
);
}
}
/********************
* Public Functions *
********************/
function upgrade(
address _implementation
)
external
{
Lib_SafeExecutionManagerWrapper.safeREQUIRE(
msg.sender,
Lib_SafeExecutionManagerWrapper.safeADDRESS(msg.sender) == Lib_SafeExecutionManagerWrapper.safeCALLER(msg.sender),
"EOAs can only upgrade their own EOA implementation"
);
_setImplementation(_implementation);
}
function getImplementation()
public
returns (
address _implementation
)
{
return address(uint160(uint256(
Lib_SafeExecutionManagerWrapper.safeSLOAD(
msg.sender,
bytes32(uint256(0))
)
)));
}
/**********************
* Internal Functions *
**********************/
function _setImplementation(
address _implementation
)
internal
{
Lib_SafeExecutionManagerWrapper.safeSSTORE(
msg.sender,
bytes32(uint256(0)),
bytes32(uint256(uint160(_implementation)))
);
}
}
\ No newline at end of file
...@@ -14,6 +14,7 @@ import { iOVM_SafetyChecker } from "../../iOVM/execution/iOVM_SafetyChecker.sol" ...@@ -14,6 +14,7 @@ import { iOVM_SafetyChecker } from "../../iOVM/execution/iOVM_SafetyChecker.sol"
/* Contract Imports */ /* Contract Imports */
import { OVM_ECDSAContractAccount } from "../accounts/OVM_ECDSAContractAccount.sol"; import { OVM_ECDSAContractAccount } from "../accounts/OVM_ECDSAContractAccount.sol";
import { OVM_ProxyEOA } from "../accounts/OVM_ProxyEOA.sol";
/** /**
* @title OVM_ExecutionManager * @title OVM_ExecutionManager
...@@ -71,7 +72,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -71,7 +72,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
globalContext = _globalContext; globalContext = _globalContext;
} }
/********************** /**********************
* Function Modifiers * * Function Modifiers *
**********************/ **********************/
...@@ -129,8 +130,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -129,8 +130,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// OVM_StateManager (expected to be an OVM_StateTransitioner). We can revert here because // OVM_StateManager (expected to be an OVM_StateTransitioner). We can revert here because
// this would make the `run` itself invalid. // this would make the `run` itself invalid.
require( require(
msg.sender == ovmStateManager.owner(), ovmStateManager.isAuthenticated(msg.sender),
"Only the owner of the ovmStateManager can call this function" "Only authenticated addresses in ovmStateManager can call this function"
); );
// Check whether we need to start a new epoch, do so if necessary. // Check whether we need to start a new epoch, do so if necessary.
...@@ -444,7 +445,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -444,7 +445,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// actually execute the transaction). // actually execute the transaction).
address eoa = ecrecover( address eoa = ecrecover(
_messageHash, _messageHash,
(_v - uint8(ovmCHAINID()) * 2) - 8, _v + 27,
_r, _r,
_s _s
); );
...@@ -463,16 +464,22 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -463,16 +464,22 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// We always need to initialize the contract with the default account values. // We always need to initialize the contract with the default account values.
_initPendingAccount(eoa); _initPendingAccount(eoa);
// Temporarily set the current address so it's easier to access on L2.
address prevADDRESS = messageContext.ovmADDRESS;
messageContext.ovmADDRESS = eoa;
// Now actually create the account and get its bytecode. We're not worried about reverts // Now actually create the account and get its bytecode. We're not worried about reverts
// (other than out of gas, which we can't capture anyway) because this contract is trusted. // (other than out of gas, which we can't capture anyway) because this contract is trusted.
OVM_ECDSAContractAccount eoaContractAccount = new OVM_ECDSAContractAccount(); OVM_ProxyEOA proxyEOA = new OVM_ProxyEOA(0x4200000000000000000000000000000000000003);
bytes memory deployedCode = Lib_EthUtils.getCode(address(eoaContractAccount));
// Reset the address now that we're done deploying.
messageContext.ovmADDRESS = prevADDRESS;
// Commit the account with its final values. // Commit the account with its final values.
_commitPendingAccount( _commitPendingAccount(
eoa, eoa,
address(eoaContractAccount), address(proxyEOA),
keccak256(deployedCode) keccak256(Lib_EthUtils.getCode(address(proxyEOA)))
); );
} }
......
...@@ -71,11 +71,26 @@ contract OVM_StateManager is iOVM_StateManager { ...@@ -71,11 +71,26 @@ contract OVM_StateManager is iOVM_StateManager {
_; _;
} }
/***************************
* Public Functions: Misc *
***************************/
function isAuthenticated(
address _address
)
override
public
view
returns (bool)
{
return (_address == owner || _address == ovmExecutionManager);
}
/*************************** /***************************
* Public Functions: Setup * * Public Functions: Setup *
***************************/ ***************************/
/** /**
* Sets the address of the OVM_ExecutionManager. * Sets the address of the OVM_ExecutionManager.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager. * @param _ovmExecutionManager Address of the OVM_ExecutionManager.
......
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol";
/**
* @title OVM_ProxySequencerEntrypoint
*/
contract OVM_ProxySequencerEntrypoint {
/*********************
* Fallback Function *
*********************/
fallback()
external
{
Lib_SafeExecutionManagerWrapper.safeDELEGATECALL(
msg.sender,
gasleft(),
_getImplementation(),
msg.data
);
}
/********************
* Public Functions *
********************/
function init(
address _implementation,
address _owner
)
external
{
Lib_SafeExecutionManagerWrapper.safeREQUIRE(
msg.sender,
_getOwner() == address(0),
"ProxySequencerEntrypoint has already been inited"
);
_setOwner(_owner);
_setImplementation(_implementation);
}
function upgrade(
address _implementation
)
external
{
Lib_SafeExecutionManagerWrapper.safeREQUIRE(
msg.sender,
_getOwner() == Lib_SafeExecutionManagerWrapper.safeCALLER(msg.sender),
"Only owner can upgrade the Entrypoint"
);
_setImplementation(_implementation);
}
/**********************
* Internal Functions *
**********************/
function _setImplementation(
address _implementation
)
internal
{
Lib_SafeExecutionManagerWrapper.safeSSTORE(
msg.sender,
bytes32(uint256(0)),
bytes32(uint256(uint160(_implementation)))
);
}
function _getImplementation()
internal
returns (
address _implementation
)
{
return address(uint160(uint256(
Lib_SafeExecutionManagerWrapper.safeSLOAD(
msg.sender,
bytes32(uint256(0))
)
)));
}
function _setOwner(
address _owner
)
internal
{
Lib_SafeExecutionManagerWrapper.safeSSTORE(
msg.sender,
bytes32(uint256(1)),
bytes32(uint256(uint160(_owner)))
);
}
function _getOwner()
internal
returns (
address _owner
)
{
return address(uint160(uint256(
Lib_SafeExecutionManagerWrapper.safeSLOAD(
msg.sender,
bytes32(uint256(1))
)
)));
}
}
pragma solidity ^0.7.0;
/* Library Imports */
import { Lib_BytesUtils } from "../../libraries/utils/Lib_BytesUtils.sol";
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_SequencerEntrypoint
*/
contract OVM_SequencerEntrypoint {
/*********
* Enums *
*********/
enum TransactionType {
NATIVE_ETH_TRANSACTION,
ETH_SIGNED_MESSAGE
}
/*********************
* Fallback Function *
*********************/
/**
* Uses a custom "compressed" format to save on calldata gas:
* calldata[00:01]: transaction type (0 == EIP 155, 2 == Eth Sign Message)
* calldata[01:33]: signature "r" parameter
* calldata[33:65]: signature "s" parameter
* calldata[66:69]: transaction gas limit
* calldata[69:72]: transaction gas price
* calldata[72:75]: transaction nonce
* calldata[75:95]: transaction target address
* calldata[95:XX]: transaction data
*/
fallback()
external
{
TransactionType transactionType = _getTransactionType(Lib_BytesUtils.toUint8(msg.data, 0));
bytes32 r = Lib_BytesUtils.toBytes32(Lib_BytesUtils.slice(msg.data, 1, 32));
bytes32 s = Lib_BytesUtils.toBytes32(Lib_BytesUtils.slice(msg.data, 33, 32));
uint8 v = Lib_BytesUtils.toUint8(msg.data, 65);
// Remainder is the transaction to execute.
bytes memory compressedTx = Lib_BytesUtils.slice(msg.data, 66);
bool isEthSignedMessage = transactionType == TransactionType.ETH_SIGNED_MESSAGE;
// Need to decompress and then re-encode the transaction based on the original encoding.
bytes memory encodedTx = Lib_OVMCodec.encodeEIP155Transaction(
Lib_OVMCodec.decompressEIP155Transaction(compressedTx),
isEthSignedMessage
);
address target = Lib_ECDSAUtils.recover(
encodedTx,
isEthSignedMessage,
uint8(v),
r,
s
);
if (Lib_SafeExecutionManagerWrapper.safeEXTCODESIZE(msg.sender, target) == 0) {
// ProxyEOA has not yet been deployed for this EOA.
bytes32 messageHash = Lib_ECDSAUtils.getMessageHash(encodedTx, isEthSignedMessage);
Lib_SafeExecutionManagerWrapper.safeCREATEEOA(msg.sender, messageHash, uint8(v), r, s);
}
// ProxyEOA has been deployed for this EOA, continue to CALL.
bytes memory callbytes = abi.encodeWithSignature(
"execute(bytes,uint8,uint8,bytes32,bytes32)",
encodedTx,
isEthSignedMessage,
uint8(v),
r,
s
);
Lib_SafeExecutionManagerWrapper.safeCALL(
msg.sender,
gasleft(),
target,
callbytes
);
}
/**********************
* Internal Functions *
**********************/
/**
* Converts a uint256 into a TransactionType enum.
* @param _transactionType Transaction type index.
* @return Transaction type enum value.
*/
function _getTransactionType(
uint8 _transactionType
)
internal
returns (
TransactionType
)
{
if (_transactionType == 0) {
return TransactionType.NATIVE_ETH_TRANSACTION;
} if (_transactionType == 2) {
return TransactionType.ETH_SIGNED_MESSAGE;
} else {
Lib_SafeExecutionManagerWrapper.safeREVERT(
msg.sender,
"Transaction type must be 0 or 2"
);
}
}
}
...@@ -21,6 +21,11 @@ interface iOVM_StateManager { ...@@ -21,6 +21,11 @@ interface iOVM_StateManager {
ITEM_COMMITTED ITEM_COMMITTED
} }
/***************************
* Public Functions: Misc *
***************************/
function isAuthenticated(address _address) external view returns (bool);
/*************************** /***************************
* Public Functions: Setup * * Public Functions: Setup *
......
...@@ -5,6 +5,7 @@ pragma experimental ABIEncoderV2; ...@@ -5,6 +5,7 @@ pragma experimental ABIEncoderV2;
/* Library Imports */ /* Library Imports */
import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol"; import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol";
import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol"; import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol";
import { Lib_BytesUtils } from "../utils/Lib_BytesUtils.sol";
/** /**
* @title Lib_OVMCodec * @title Lib_OVMCodec
...@@ -32,8 +33,8 @@ library Lib_OVMCodec { ...@@ -32,8 +33,8 @@ library Lib_OVMCodec {
*********/ *********/
enum EOASignatureType { enum EOASignatureType {
ETH_SIGNED_MESSAGE, EIP155_TRANSACTON,
NATIVE_TRANSACTON ETH_SIGNED_MESSAGE
} }
enum QueueOrigin { enum QueueOrigin {
...@@ -99,11 +100,14 @@ library Lib_OVMCodec { ...@@ -99,11 +100,14 @@ library Lib_OVMCodec {
uint40 blockNumber; uint40 blockNumber;
} }
struct EOATransaction { struct EIP155Transaction {
address target;
uint256 nonce; uint256 nonce;
uint256 gasPrice;
uint256 gasLimit; uint256 gasLimit;
address to;
uint256 value;
bytes data; bytes data;
uint256 chainId;
} }
...@@ -116,25 +120,118 @@ library Lib_OVMCodec { ...@@ -116,25 +120,118 @@ library Lib_OVMCodec {
* @param _transaction Encoded EOA transaction. * @param _transaction Encoded EOA transaction.
* @return _decoded Transaction decoded into a struct. * @return _decoded Transaction decoded into a struct.
*/ */
function decodeEOATransaction( function decodeEIP155Transaction(
bytes memory _transaction bytes memory _transaction,
bool _isEthSignedMessage
) )
internal internal
pure pure
returns ( returns (
EOATransaction memory _decoded EIP155Transaction memory _decoded
) )
{ {
Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_transaction); if (_isEthSignedMessage) {
(
uint _nonce,
uint _gasLimit,
uint _gasPrice,
uint _chainId,
address _to,
bytes memory _data
) = abi.decode(
_transaction,
(uint, uint, uint, uint, address ,bytes)
);
return EIP155Transaction({
nonce: _nonce,
gasPrice: _gasPrice,
gasLimit: _gasLimit,
to: _to,
value: 0,
data: _data,
chainId: _chainId
});
} else {
Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_transaction);
return EIP155Transaction({
nonce: Lib_RLPReader.readUint256(decoded[0]),
gasPrice: Lib_RLPReader.readUint256(decoded[1]),
gasLimit: Lib_RLPReader.readUint256(decoded[2]),
to: Lib_RLPReader.readAddress(decoded[3]),
value: Lib_RLPReader.readUint256(decoded[4]),
data: Lib_RLPReader.readBytes(decoded[5]),
chainId: Lib_RLPReader.readUint256(decoded[6])
});
}
}
return EOATransaction({ function decompressEIP155Transaction(
nonce: Lib_RLPReader.readUint256(decoded[0]), bytes memory _transaction
gasLimit: Lib_RLPReader.readUint256(decoded[2]), )
target: Lib_RLPReader.readAddress(decoded[3]), internal
data: Lib_RLPReader.readBytes(decoded[5]) pure
returns (
EIP155Transaction memory _decompressed
)
{
return EIP155Transaction({
gasLimit: Lib_BytesUtils.toUint24(_transaction, 0),
gasPrice: uint256(Lib_BytesUtils.toUint24(_transaction, 3)) * 1000000,
nonce: Lib_BytesUtils.toUint24(_transaction, 6),
to: Lib_BytesUtils.toAddress(_transaction, 9),
data: Lib_BytesUtils.slice(_transaction, 29),
chainId: 420,
value: 0
}); });
} }
/**
* Encodes an EOA transaction back into the original transaction.
* @param _transaction EIP155transaction to encode.
* @param _isEthSignedMessage Whether or not this was an eth signed message.
* @return Encoded transaction.
*/
function encodeEIP155Transaction(
EIP155Transaction memory _transaction,
bool _isEthSignedMessage
)
internal
pure
returns (
bytes memory
)
{
if (_isEthSignedMessage) {
return abi.encode(
_transaction.nonce,
_transaction.gasLimit,
_transaction.gasPrice,
_transaction.chainId,
_transaction.to,
_transaction.data
);
} else {
bytes[] memory raw = new bytes[](9);
raw[0] = Lib_RLPWriter.writeUint(_transaction.nonce);
raw[1] = Lib_RLPWriter.writeUint(_transaction.gasPrice);
raw[2] = Lib_RLPWriter.writeUint(_transaction.gasLimit);
if (_transaction.to == address(0)) {
raw[3] = Lib_RLPWriter.writeBytes('');
} else {
raw[3] = Lib_RLPWriter.writeAddress(_transaction.to);
}
raw[4] = Lib_RLPWriter.writeUint(0);
raw[5] = Lib_RLPWriter.writeBytes(_transaction.data);
raw[6] = Lib_RLPWriter.writeUint(_transaction.chainId);
raw[7] = Lib_RLPWriter.writeBytes(bytes(''));
raw[8] = Lib_RLPWriter.writeBytes(bytes(''));
return Lib_RLPWriter.writeList(raw);
}
}
/** /**
* Encodes a standard OVM transaction. * Encodes a standard OVM transaction.
* @param _transaction OVM transaction to encode. * @param _transaction OVM transaction to encode.
......
...@@ -192,6 +192,42 @@ library Lib_BytesUtils { ...@@ -192,6 +192,42 @@ library Lib_BytesUtils {
return uint256(toBytes32(_bytes)); return uint256(toBytes32(_bytes));
} }
function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) {
require(_start + 3 >= _start, "toUint24_overflow");
require(_bytes.length >= _start + 3 , "toUint24_outOfBounds");
uint24 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x3), _start))
}
return tempUint;
}
function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
require(_start + 1 >= _start, "toUint8_overflow");
require(_bytes.length >= _start + 1 , "toUint8_outOfBounds");
uint8 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}
return tempUint;
}
function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
require(_start + 20 >= _start, "toAddress_overflow");
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function toNibbles( function toNibbles(
bytes memory _bytes bytes memory _bytes
) )
......
...@@ -17,7 +17,6 @@ library Lib_ECDSAUtils { ...@@ -17,7 +17,6 @@ library Lib_ECDSAUtils {
* @param _v Signature `v` parameter. * @param _v Signature `v` parameter.
* @param _r Signature `r` parameter. * @param _r Signature `r` parameter.
* @param _s Signature `s` parameter. * @param _s Signature `s` parameter.
* @param _chainId Chain ID parameter.
* @return _sender Signer address. * @return _sender Signer address.
*/ */
function recover( function recover(
...@@ -25,8 +24,7 @@ library Lib_ECDSAUtils { ...@@ -25,8 +24,7 @@ library Lib_ECDSAUtils {
bool _isEthSignedMessage, bool _isEthSignedMessage,
uint8 _v, uint8 _v,
bytes32 _r, bytes32 _r,
bytes32 _s, bytes32 _s
uint256 _chainId
) )
internal internal
pure pure
...@@ -34,24 +32,29 @@ library Lib_ECDSAUtils { ...@@ -34,24 +32,29 @@ library Lib_ECDSAUtils {
address _sender address _sender
) )
{ {
bytes32 messageHash; bytes32 messageHash = getMessageHash(_message, _isEthSignedMessage);
uint8 v;
if (_isEthSignedMessage) {
messageHash = getEthSignedMessageHash(_message);
v = _v;
} else {
messageHash = getNativeMessageHash(_message);
v = (_v - uint8(_chainId) * 2) - 8;
}
return ecrecover( return ecrecover(
messageHash, messageHash,
v, _v + 27,
_r, _r,
_s _s
); );
} }
function getMessageHash(
bytes memory _message,
bool _isEthSignedMessage
)
internal
pure
returns (bytes32) {
if (_isEthSignedMessage) {
return getEthSignedMessageHash(_message);
}
return getNativeMessageHash(_message);
}
/************************************* /*************************************
* Private Functions: ECDSA Recovery * * Private Functions: ECDSA Recovery *
......
...@@ -44,6 +44,40 @@ library Lib_SafeExecutionManagerWrapper { ...@@ -44,6 +44,40 @@ library Lib_SafeExecutionManagerWrapper {
return abi.decode(returndata, (bool, bytes)); return abi.decode(returndata, (bool, bytes));
} }
/**
* 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 safeDELEGATECALL(
address _ovmExecutionManager,
uint256 _gasLimit,
address _target,
bytes memory _calldata
)
internal
returns (
bool _success,
bytes memory _returndata
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmDELEGATECALL(uint256,address,bytes)",
_gasLimit,
_target,
_calldata
)
);
return abi.decode(returndata, (bool, bytes));
}
/** /**
* Performs an ovmCREATE and the necessary safety checks. * Performs an ovmCREATE and the necessary safety checks.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager. * @param _ovmExecutionManager Address of the OVM_ExecutionManager.
...@@ -73,6 +107,32 @@ library Lib_SafeExecutionManagerWrapper { ...@@ -73,6 +107,32 @@ library Lib_SafeExecutionManagerWrapper {
return abi.decode(returndata, (address)); return abi.decode(returndata, (address));
} }
/**
* Performs an ovmEXTCODESIZE and the necessary safety checks.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _contract Address of the contract to query the size of.
* @return _EXTCODESIZE Size of the requested contract in bytes.
*/
function safeEXTCODESIZE(
address _ovmExecutionManager,
address _contract
)
internal
returns (
uint256 _EXTCODESIZE
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmEXTCODESIZE(address)",
_contract
)
);
return abi.decode(returndata, (uint256));
}
/** /**
* Performs a safe ovmCHAINID call. * Performs a safe ovmCHAINID call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager. * @param _ovmExecutionManager Address of the OVM_ExecutionManager.
...@@ -96,6 +156,29 @@ library Lib_SafeExecutionManagerWrapper { ...@@ -96,6 +156,29 @@ library Lib_SafeExecutionManagerWrapper {
return abi.decode(returndata, (uint256)); return abi.decode(returndata, (uint256));
} }
/**
* Performs a safe ovmCALLER call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @return _CALLER Result of calling ovmCALLER.
*/
function safeCALLER(
address _ovmExecutionManager
)
internal
returns (
address _CALLER
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmCALLER()"
)
);
return abi.decode(returndata, (address));
}
/** /**
* Performs a safe ovmADDRESS call. * Performs a safe ovmADDRESS call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager. * @param _ovmExecutionManager Address of the OVM_ExecutionManager.
...@@ -162,6 +245,118 @@ library Lib_SafeExecutionManagerWrapper { ...@@ -162,6 +245,118 @@ library Lib_SafeExecutionManagerWrapper {
); );
} }
/**
* Performs a safe ovmCREATEEOA call.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _messageHash Message hash which was signed by EOA
* @param _v v value of signature (0 or 1)
* @param _r r value of signature
* @param _s s value of signature
*/
function safeCREATEEOA(
address _ovmExecutionManager,
bytes32 _messageHash,
uint8 _v,
bytes32 _r,
bytes32 _s
)
internal
{
_safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmCREATEEOA(bytes32,uint8,bytes32,bytes32)",
_messageHash,
_v,
_r,
_s
)
);
}
/**
* Performs a safe REVERT.
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _reason String revert reason to pass along with the REVERT.
*/
function safeREVERT(
address _ovmExecutionManager,
string memory _reason
)
internal
{
_safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmREVERT(bytes)",
bytes(_reason)
)
);
}
/**
* Performs a safe "require".
* @param _ovmExecutionManager Address of the OVM_ExecutionManager.
* @param _condition Boolean condition that must be true or will revert.
* @param _reason String revert reason to pass along with the REVERT.
*/
function safeREQUIRE(
address _ovmExecutionManager,
bool _condition,
string memory _reason
)
internal
{
if (!_condition) {
safeREVERT(
_ovmExecutionManager,
_reason
);
}
}
/**
* Performs a safe ovmSLOAD call.
*/
function safeSLOAD(
address _ovmExecutionManager,
bytes32 _key
)
internal
returns (
bytes32
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmSLOAD(bytes32)",
_key
)
);
return abi.decode(returndata, (bytes32));
}
/**
* Performs a safe ovmSSTORE call.
*/
function safeSSTORE(
address _ovmExecutionManager,
bytes32 _key,
bytes32 _value
)
internal
{
_safeExecutionManagerInteraction(
_ovmExecutionManager,
abi.encodeWithSignature(
"ovmSSTORE(bytes32,bytes32)",
_key,
_value
)
);
}
/********************* /*********************
* Private Functions * * Private Functions *
......
...@@ -44,12 +44,18 @@ contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -44,12 +44,18 @@ contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
) )
{ {
address ovmExecutionManager = msg.sender; address ovmExecutionManager = msg.sender;
bool isEthSign = _signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE;
Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction(_transaction, isEthSign);
// Skip signature validation in this mock. // Need to make sure that the transaction nonce is right.
Lib_OVMCodec.EOATransaction memory decodedTx = Lib_OVMCodec.decodeEOATransaction(_transaction); Lib_SafeExecutionManagerWrapper.safeREQUIRE(
msg.sender,
decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager),
"Transaction nonce does not match the expected nonce."
);
// Contract creations are signalled by sending a transaction to the zero address. // Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.target == address(0)) { if (decodedTx.to == address(0)) {
address created = Lib_SafeExecutionManagerWrapper.safeCREATE( address created = Lib_SafeExecutionManagerWrapper.safeCREATE(
ovmExecutionManager, ovmExecutionManager,
decodedTx.gasLimit, decodedTx.gasLimit,
...@@ -59,21 +65,36 @@ contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -59,21 +65,36 @@ contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// If the created address is the ZERO_ADDRESS then we know the deployment failed, though not why // If the created address is the ZERO_ADDRESS then we know the deployment failed, though not why
return (created != address(0), abi.encode(created)); return (created != address(0), abi.encode(created));
} else { } else {
_incrementNonce(); // We only want to bump the nonce for `ovmCALL` because `ovmCREATE` automatically bumps
(_success, _returndata) = Lib_SafeExecutionManagerWrapper.safeCALL( // 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 + 1);
return Lib_SafeExecutionManagerWrapper.safeCALL(
ovmExecutionManager, ovmExecutionManager,
decodedTx.gasLimit, decodedTx.gasLimit,
decodedTx.target, decodedTx.to,
decodedTx.data decodedTx.data
); );
} }
} }
function _incrementNonce() function qall(
internal uint256 _gasLimit,
address _to,
bytes memory _data
)
public
returns (
bool _success,
bytes memory _returndata
)
{ {
address ovmExecutionManager = msg.sender; return Lib_SafeExecutionManagerWrapper.safeCALL(
uint nonce = Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager) + 1; msg.sender,
Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, nonce); _gasLimit,
_to,
_data
);
} }
} }
...@@ -17,6 +17,27 @@ contract Helper_PrecompileCaller is Helper_SimpleProxy { ...@@ -17,6 +17,27 @@ contract Helper_PrecompileCaller is Helper_SimpleProxy {
} }
} }
function callPrecompileAbi(
address _precompile,
bytes memory _data
)
public
returns (
bytes memory
)
{
bool success;
bytes memory returndata;
if (msg.sender == owner) {
(success, returndata) = _precompile.call(_data);
} else {
(success, returndata) = target.call(msg.data);
}
require(success, "Precompile call reverted");
return returndata;
}
function getL1MessageSender( function getL1MessageSender(
address _precompile, address _precompile,
bytes memory _data bytes memory _data
......
...@@ -10,16 +10,17 @@ import { Lib_OVMCodec } from "../../optimistic-ethereum/libraries/codec/Lib_OVMC ...@@ -10,16 +10,17 @@ import { Lib_OVMCodec } from "../../optimistic-ethereum/libraries/codec/Lib_OVMC
*/ */
contract TestLib_OVMCodec { contract TestLib_OVMCodec {
function decodeEOATransaction( function decodeEIP155Transaction(
bytes memory _transaction bytes memory _transaction,
bool _isEthSignedMessage
) )
public public
pure pure
returns ( returns (
Lib_OVMCodec.EOATransaction memory _decoded Lib_OVMCodec.EIP155Transaction memory _decoded
) )
{ {
return Lib_OVMCodec.decodeEOATransaction(_transaction); return Lib_OVMCodec.decodeEIP155Transaction(_transaction, _isEthSignedMessage);
} }
function encodeTransaction( function encodeTransaction(
...@@ -45,4 +46,16 @@ contract TestLib_OVMCodec { ...@@ -45,4 +46,16 @@ contract TestLib_OVMCodec {
{ {
return Lib_OVMCodec.hashTransaction(_transaction); return Lib_OVMCodec.hashTransaction(_transaction);
} }
function decompressEIP155Transaction(
bytes memory _transaction
)
public
pure
returns (
Lib_OVMCodec.EIP155Transaction memory _decompressed
)
{
return Lib_OVMCodec.decompressEIP155Transaction(_transaction);
}
} }
...@@ -14,8 +14,7 @@ contract TestLib_ECDSAUtils { ...@@ -14,8 +14,7 @@ contract TestLib_ECDSAUtils {
bool _isEthSignedMessage, bool _isEthSignedMessage,
uint8 _v, uint8 _v,
bytes32 _r, bytes32 _r,
bytes32 _s, bytes32 _s
uint256 _chainId
) )
public public
pure pure
...@@ -28,8 +27,7 @@ contract TestLib_ECDSAUtils { ...@@ -28,8 +27,7 @@ contract TestLib_ECDSAUtils {
_isEthSignedMessage, _isEthSignedMessage,
_v, _v,
_r, _r,
_s, _s
_chainId
); );
} }
} }
...@@ -129,6 +129,12 @@ export const makeContractDeployConfig = async ( ...@@ -129,6 +129,12 @@ export const makeContractDeployConfig = async (
OVM_ECDSAContractAccount: { OVM_ECDSAContractAccount: {
factory: getContractFactory('OVM_ECDSAContractAccount'), factory: getContractFactory('OVM_ECDSAContractAccount'),
}, },
OVM_SequencerEntrypoint: {
factory: getContractFactory('OVM_SequencerEntrypoint'),
},
OVM_ProxySequencerEntrypoint: {
factory: getContractFactory('OVM_ProxySequencerEntrypoint'),
},
mockOVM_ECDSAContractAccount: { mockOVM_ECDSAContractAccount: {
factory: getContractFactory('mockOVM_ECDSAContractAccount'), factory: getContractFactory('mockOVM_ECDSAContractAccount'),
}, },
......
...@@ -44,7 +44,8 @@ const getStorageDump = async ( ...@@ -44,7 +44,8 @@ const getStorageDump = async (
const stream = trie.createReadStream() const stream = trie.createReadStream()
stream.on('data', (val: any) => { stream.on('data', (val: any) => {
storage[val.key.toString('hex')] = val.value.toString('hex').slice(2) storage['0x' + val.key.toString('hex')] =
'0x' + val.value.toString('hex').slice(2)
}) })
stream.on('end', () => { stream.on('end', () => {
...@@ -131,6 +132,10 @@ export const makeStateDump = async (): Promise<any> => { ...@@ -131,6 +132,10 @@ export const makeStateDump = async (): Promise<any> => {
'OVM_DeployerWhitelist', 'OVM_DeployerWhitelist',
'OVM_L1MessageSender', 'OVM_L1MessageSender',
'OVM_L2ToL1MessagePasser', 'OVM_L2ToL1MessagePasser',
'OVM_ProxyEOA',
'OVM_ECDSAContractAccount',
'OVM_ProxySequencerEntrypoint',
'OVM_SequencerEntrypoint',
'OVM_L2CrossDomainMessenger', 'OVM_L2CrossDomainMessenger',
'OVM_SafetyChecker', 'OVM_SafetyChecker',
'OVM_ExecutionManager', 'OVM_ExecutionManager',
...@@ -143,6 +148,9 @@ export const makeStateDump = async (): Promise<any> => { ...@@ -143,6 +148,9 @@ export const makeStateDump = async (): Promise<any> => {
OVM_L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000', OVM_L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000',
OVM_L1MessageSender: '0x4200000000000000000000000000000000000001', OVM_L1MessageSender: '0x4200000000000000000000000000000000000001',
OVM_DeployerWhitelist: '0x4200000000000000000000000000000000000002', OVM_DeployerWhitelist: '0x4200000000000000000000000000000000000002',
OVM_ECDSAContractAccount: '0x4200000000000000000000000000000000000003',
OVM_ProxySequencerEntrypoint: '0x4200000000000000000000000000000000000004',
OVM_SequencerEntrypoint: '0x4200000000000000000000000000000000000005',
} }
const deploymentResult = await deploy(config) const deploymentResult = await deploy(config)
......
const hexRegex = /^(0x)?[0-9a-fA-F]*$/
/** /**
* Generates a hex string of repeated bytes. * Generates a hex string of repeated bytes.
* @param byte Byte to repeat. * @param byte Byte to repeat.
...@@ -29,3 +31,59 @@ export const remove0x = (str: string): string => { ...@@ -29,3 +31,59 @@ export const remove0x = (str: string): string => {
return str return str
} }
} }
/**
* Returns whether or not the provided string is a hex string.
*
* @param str The string to test.
* @returns True if the provided string is a hex string, false otherwise.
*/
export const isHexString = (str: string): boolean => {
return hexRegex.test(str)
}
/**
* Converts a hex string to a buffer
* @param hexString the hex string to be converted
* @returns the hexString as a buffer.
*/
export const hexStrToBuf = (hexString: string): Buffer => {
if (!isHexString(hexString)) {
throw new RangeError(`Invalid hex string [${hexString}]`)
}
if (hexString.length % 2 !== 0) {
throw new RangeError(
`Invalid hex string -- odd number of characters: [${hexString}]`
)
}
return Buffer.from(remove0x(hexString), 'hex')
}
/**
* Converts a JavaScript number to a big-endian hex string.
* @param number the JavaScript number to be converted.
* @param padToBytes the number of numeric bytes the resulting string should be, -1 if no padding should be done.
* @returns the JavaScript number as a string.
*/
export const numberToHexString = (
number: number,
padToBytes: number = -1
): string => {
let str = number.toString(16)
if (padToBytes > 0 || str.length < padToBytes * 2) {
str = `${'0'.repeat(padToBytes * 2 - str.length)}${str}`
}
return add0x(str)
}
/**
* Adds "0x" to the start of a string if necessary.
* @param str String to modify.
* @returns the string with "0x".
*/
export const add0x = (str: string): string => {
if (str === undefined) {
return str
}
return str.startsWith('0x') ? str : '0x' + str
}
/* tslint:disable:no-empty */
import { expect } from '../../../setup' import { expect } from '../../../setup'
/* External Imports */
import { ethers, waffle } from '@nomiclabs/buidler'
import { ContractFactory, Contract, Wallet } from 'ethers'
import { MockContract, smockit } from '@eth-optimism/smock'
import { NON_ZERO_ADDRESS, ZERO_ADDRESS } from '../../../helpers/constants'
import {
serializeNativeTransaction,
signNativeTransaction,
DEFAULT_EIP155_TX,
serializeEthSignTransaction,
signEthSignMessage,
EIP155Transaction,
} from '../../../helpers'
import { defaultAbiCoder } from 'ethers/lib/utils'
const callPrecompile = async (
Helper_PrecompileCaller: Contract,
precompile: Contract,
functionName: string,
functionParams?: any[]
): Promise<any> => {
return Helper_PrecompileCaller.callPrecompile(
precompile.address,
precompile.interface.encodeFunctionData(functionName, functionParams || [])
)
}
describe('OVM_ECDSAContractAccount', () => { describe('OVM_ECDSAContractAccount', () => {
describe('execute', () => { let wallet: Wallet
describe('when provided an invalid signature', () => { let badWallet: Wallet
it('should revert', async () => {}) before(async () => {
const provider = waffle.provider
;[wallet, badWallet] = provider.getWallets()
})
let Mock__OVM_ExecutionManager: MockContract
let Helper_PrecompileCaller: Contract
before(async () => {
Mock__OVM_ExecutionManager = smockit(
await ethers.getContractFactory('OVM_ExecutionManager')
)
Helper_PrecompileCaller = await (
await ethers.getContractFactory('Helper_PrecompileCaller')
).deploy()
Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address)
})
let Factory__OVM_ECDSAContractAccount: ContractFactory
before(async () => {
Factory__OVM_ECDSAContractAccount = await ethers.getContractFactory(
'OVM_ECDSAContractAccount'
)
})
let OVM_ECDSAContractAccount: Contract
beforeEach(async () => {
OVM_ECDSAContractAccount = await Factory__OVM_ECDSAContractAccount.deploy()
Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with(
await wallet.getAddress()
)
Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420)
Mock__OVM_ExecutionManager.smocked.ovmGETNONCE.will.return.with(100)
Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with([true, '0x'])
Mock__OVM_ExecutionManager.smocked.ovmCREATE.will.return.with(
NON_ZERO_ADDRESS
)
Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with(
NON_ZERO_ADDRESS
)
})
describe('fallback()', () => {
it(`should successfully execute an EIP155Transaction`, async () => {
const message = serializeNativeTransaction(DEFAULT_EIP155_TX)
const sig = await signNativeTransaction(wallet, DEFAULT_EIP155_TX)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ECDSAContractAccount,
'execute',
[
message,
0, // isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
]
)
// The ovmCALL is the 2nd call because the first call transfers the fee.
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1]
expect(ovmCALL._gasLimit).to.equal(DEFAULT_EIP155_TX.gasLimit)
expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to)
expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data)
const ovmSETNONCE: any =
Mock__OVM_ExecutionManager.smocked.ovmSETNONCE.calls[0]
expect(ovmSETNONCE._nonce).to.equal(DEFAULT_EIP155_TX.nonce + 1)
})
it(`should successfully execute an ETHSignedTransaction`, async () => {
const message = serializeEthSignTransaction(DEFAULT_EIP155_TX)
const sig = await signEthSignMessage(wallet, DEFAULT_EIP155_TX)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ECDSAContractAccount,
'execute',
[
message,
1, //isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
]
)
// The ovmCALL is the 2nd call because the first call transfers the fee.
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1]
expect(ovmCALL._gasLimit).to.equal(DEFAULT_EIP155_TX.gasLimit)
expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to)
expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data)
const ovmSETNONCE: any =
Mock__OVM_ExecutionManager.smocked.ovmSETNONCE.calls[0]
expect(ovmSETNONCE._nonce).to.equal(DEFAULT_EIP155_TX.nonce + 1)
}) })
describe('when provided a valid signature', () => { it(`should ovmCREATE if EIP155Transaction.to is zero address`, async () => {
describe('when provided an invalid nonce', () => { const createTx = { ...DEFAULT_EIP155_TX, to: '' }
it('should revert', async () => {}) const message = serializeNativeTransaction(createTx)
}) const sig = await signNativeTransaction(wallet, createTx)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ECDSAContractAccount,
'execute',
[
message,
0, //isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
]
)
const ovmCREATE: any =
Mock__OVM_ExecutionManager.smocked.ovmCREATE.calls[0]
expect(ovmCREATE._bytecode).to.equal(createTx.data)
})
it(`should revert on invalid signature`, async () => {
const message = serializeNativeTransaction(DEFAULT_EIP155_TX)
const sig = await signNativeTransaction(badWallet, DEFAULT_EIP155_TX)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ECDSAContractAccount,
'execute',
[
message,
0, //isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
]
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal(
'Signature provided for EOA transaction execution is invalid.'
)
})
describe('when provided a valid nonce', () => { it(`should revert on incorrect nonce`, async () => {
describe('when executing ovmCREATE', () => { const alteredNonceTx = DEFAULT_EIP155_TX
it('should return the address of the created contract', async () => {}) alteredNonceTx.nonce = 99
}) const message = serializeNativeTransaction(alteredNonceTx)
const sig = await signNativeTransaction(wallet, alteredNonceTx)
describe('when executing ovmCALL', () => { await callPrecompile(
it('should return the result of the call', async () => {}) Helper_PrecompileCaller,
}) OVM_ECDSAContractAccount,
}) 'execute',
[
message,
0, //isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
]
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal(
'Transaction nonce does not match the expected nonce.'
)
}) })
}) })
}) })
import { expect } from '../../../setup'
/* External Imports */
import { ethers, waffle } from '@nomiclabs/buidler'
import { ContractFactory, Contract, Wallet } from 'ethers'
import { MockContract, smockit } from '@eth-optimism/smock'
import { NON_ZERO_ADDRESS } from '../../../helpers/constants'
import { AbiCoder, keccak256 } from 'ethers/lib/utils'
import {
DEFAULT_EIP155_TX,
remove0x,
serializeNativeTransaction,
signNativeTransaction,
} from '../../../helpers'
import { getContractInterface } from '../../../../src'
const callPrecompile = async (
Helper_PrecompileCaller: Contract,
precompile: Contract,
functionName: string,
functionParams?: any[],
ethCall: boolean = false
): Promise<any> => {
if (ethCall) {
return Helper_PrecompileCaller.callStatic.callPrecompileAbi(
precompile.address,
precompile.interface.encodeFunctionData(
functionName,
functionParams || []
)
)
}
return Helper_PrecompileCaller.callPrecompile(
precompile.address,
precompile.interface.encodeFunctionData(functionName, functionParams || [])
)
}
const addrToBytes32 = (addr: string) => '0x' + '00'.repeat(12) + remove0x(addr)
const eoaDefaultAddr = '0x4200000000000000000000000000000000000003'
describe('OVM_ProxyEOA', () => {
let wallet: Wallet
before(async () => {
const provider = waffle.provider
;[wallet] = provider.getWallets()
})
let Mock__OVM_ExecutionManager: MockContract
let Mock__OVM_ECDSAContractAccount: MockContract
let Helper_PrecompileCaller: Contract
before(async () => {
Mock__OVM_ExecutionManager = smockit(
await ethers.getContractFactory('OVM_ExecutionManager')
)
Helper_PrecompileCaller = await (
await ethers.getContractFactory('Helper_PrecompileCaller')
).deploy()
Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address)
Mock__OVM_ECDSAContractAccount = smockit(
await ethers.getContractFactory('OVM_ECDSAContractAccount')
)
})
let OVM_ProxyEOAFactory: ContractFactory
before(async () => {
OVM_ProxyEOAFactory = await ethers.getContractFactory('OVM_ProxyEOA')
})
let OVM_ProxyEOA: Contract
beforeEach(async () => {
OVM_ProxyEOA = await OVM_ProxyEOAFactory.deploy(eoaDefaultAddr)
Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with(
OVM_ProxyEOA.address
)
Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with(
OVM_ProxyEOA.address
)
})
describe('getImplementation()', () => {
it(`should be created with implementation at precompile address`, async () => {
const eoaDefaultAddrBytes32 = addrToBytes32(eoaDefaultAddr)
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with(
eoaDefaultAddrBytes32
)
const implAddrBytes32 = await callPrecompile(
Helper_PrecompileCaller,
OVM_ProxyEOA,
'getImplementation',
[],
true
)
expect(implAddrBytes32).to.equal(eoaDefaultAddrBytes32)
})
})
describe('upgrade()', () => {
it(`should upgrade the proxy implementation`, async () => {
const newImpl = `0x${'81'.repeat(20)}`
const newImplBytes32 = addrToBytes32(newImpl)
await callPrecompile(Helper_PrecompileCaller, OVM_ProxyEOA, 'upgrade', [
newImpl,
])
const ovmSSTORE: any =
Mock__OVM_ExecutionManager.smocked.ovmSSTORE.calls[0]
expect(ovmSSTORE._key).to.equal(`0x${'00'.repeat(32)}`)
expect(ovmSSTORE._value).to.equal(newImplBytes32)
})
it(`should not allow upgrade of the proxy implementation by another account`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with(
await wallet.getAddress()
)
const newImpl = `0x${'81'.repeat(20)}`
await callPrecompile(Helper_PrecompileCaller, OVM_ProxyEOA, 'upgrade', [
newImpl,
])
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal(
'EOAs can only upgrade their own EOA implementation'
)
})
})
describe('fallback()', () => {
it(`should call delegateCall with right calldata`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with(
addrToBytes32(Mock__OVM_ECDSAContractAccount.address)
)
Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.will.return.with([
true,
'0x1234',
])
const calldata = '0xdeadbeef'
await Helper_PrecompileCaller.callPrecompile(
OVM_ProxyEOA.address,
calldata
)
const ovmDELEGATECALL: any =
Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.calls[0]
expect(ovmDELEGATECALL._address).to.equal(
Mock__OVM_ECDSAContractAccount.address
)
expect(ovmDELEGATECALL._calldata).to.equal(calldata)
})
it.skip(`should return data from fallback`, async () => {
//TODO test return data from fallback
})
it.skip(`should revert in fallback`, async () => {
//TODO test reversion from fallback
})
})
})
...@@ -42,9 +42,9 @@ const deployProxyXDomainMessenger = async ( ...@@ -42,9 +42,9 @@ const deployProxyXDomainMessenger = async (
'OVM_L1CrossDomainMessenger', 'OVM_L1CrossDomainMessenger',
l1XDomainMessenger.address l1XDomainMessenger.address
) )
const proxy = await (await ethers.getContractFactory( const proxy = await (
'Lib_ResolvedDelegateProxy' await ethers.getContractFactory('Lib_ResolvedDelegateProxy')
)).deploy(addressManager.address, 'OVM_L1CrossDomainMessenger') ).deploy(addressManager.address, 'OVM_L1CrossDomainMessenger')
return l1XDomainMessenger.attach(proxy.address) return l1XDomainMessenger.attach(proxy.address)
} }
...@@ -105,7 +105,10 @@ describe('OVM_L1CrossDomainMessenger', () => { ...@@ -105,7 +105,10 @@ describe('OVM_L1CrossDomainMessenger', () => {
beforeEach(async () => { beforeEach(async () => {
const xDomainMessenerImpl = await Factory__OVM_L1CrossDomainMessenger.deploy() const xDomainMessenerImpl = await Factory__OVM_L1CrossDomainMessenger.deploy()
// We use an upgradable proxy for the XDomainMessenger--deploy & set up the proxy. // We use an upgradable proxy for the XDomainMessenger--deploy & set up the proxy.
OVM_L1CrossDomainMessenger = await deployProxyXDomainMessenger(AddressManager, xDomainMessenerImpl) OVM_L1CrossDomainMessenger = await deployProxyXDomainMessenger(
AddressManager,
xDomainMessenerImpl
)
await OVM_L1CrossDomainMessenger.initialize(AddressManager.address) await OVM_L1CrossDomainMessenger.initialize(AddressManager.address)
}) })
......
/* Internal Imports */
import {
ExecutionManagerTestRunner,
TestDefinition,
OVM_TX_GAS_LIMIT,
NON_NULL_BYTES32,
REVERT_FLAGS,
VERIFIED_EMPTY_CONTRACT_HASH,
} from '../../../../helpers'
const test_ovmCREATEEOA: TestDefinition = {
name: 'Basic tests for CREATEEOA',
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',
},
'0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff': {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
},
verifiedContractStorage: {
$DUMMY_OVM_ADDRESS_1: {
[NON_NULL_BYTES32]: true,
},
},
},
},
parameters: [
{
name: 'ovmCREATEEOA, ovmEXTCODESIZE(CREATED)',
steps: [
{
functionName: 'ovmCREATEEOA',
functionParams: {
_messageHash:
'0x92d658d25f963af824e9d4bd533c165773d4a694a67d88135d119d5bca97c001',
_v: 1,
_r:
'0x73757c671fae2c3fb6825766c724b7715720bda4b309d3612f2c623364556967',
_s:
'0x2fc9b7222783390b9f10e22e92a52871beaff2613193d6e2dbf18d0e2d2eb8ff',
},
expectedReturnStatus: true,
expectedReturnValue: undefined,
},
{
functionName: 'ovmEXTCODESIZE',
functionParams: {
address: '0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff',
},
expectedReturnStatus: true,
expectedReturnValue: 1638,
},
],
},
{
name: 'ovmCALL(ADDRESS_1) => ovmGETNONCE',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmGETNONCE',
expectedReturnValue: 0,
},
],
},
expectedReturnStatus: true,
},
],
},
{
name: 'ovmCALL(ADDRESS_1) => ovmSETNONCE(3) => ovmGETNONCE',
steps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmSETNONCE',
functionParams: {
_nonce: '0x03',
},
expectedReturnStatus: true,
},
{
functionName: 'ovmGETNONCE',
expectedReturnValue: 3,
},
],
},
expectedReturnStatus: true,
},
],
},
],
}
const runner = new ExecutionManagerTestRunner()
runner.run(test_ovmCREATEEOA)
import { expect } from '../../../setup'
/* External Imports */
import { ethers, waffle } from '@nomiclabs/buidler'
import { ContractFactory, Contract, Wallet } from 'ethers'
import { MockContract, smockit } from '@eth-optimism/smock'
import {
encodeSequencerCalldata,
DEFAULT_EIP155_TX,
ZERO_ADDRESS,
remove0x,
} from '../../../helpers'
const callPrecompile = async (
Helper_PrecompileCaller: Contract,
precompile: Contract,
functionName: string,
functionParams?: any[]
): Promise<any> => {
return Helper_PrecompileCaller.callPrecompile(
precompile.address,
precompile.interface.encodeFunctionData(functionName, functionParams || [])
)
}
const addrToBytes32 = (addr: string) =>
'0x' + '00'.repeat(12) + remove0x(addr.toLowerCase())
describe('OVM_ProxySequencerEntrypoint', () => {
let wallet: Wallet
before(async () => {
const provider = waffle.provider
;[wallet] = provider.getWallets()
})
let Factory__OVM_ProxySequencerEntrypoint: ContractFactory
before(async () => {
Factory__OVM_ProxySequencerEntrypoint = await ethers.getContractFactory(
'OVM_ProxySequencerEntrypoint'
)
})
let Mock__OVM_ExecutionManager: MockContract
let Helper_PrecompileCaller: Contract
let OVM_SequencerEntrypoint: Contract
before(async () => {
Mock__OVM_ExecutionManager = smockit(
await ethers.getContractFactory('OVM_ExecutionManager')
)
Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with(
await wallet.getAddress()
)
Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(0)
Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420)
Helper_PrecompileCaller = await (
await ethers.getContractFactory('Helper_PrecompileCaller')
).deploy()
Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address)
OVM_SequencerEntrypoint = await (
await ethers.getContractFactory('OVM_SequencerEntrypoint')
).deploy()
})
let OVM_ProxySequencerEntrypoint: Contract
beforeEach(async () => {
OVM_ProxySequencerEntrypoint = await Factory__OVM_ProxySequencerEntrypoint.deploy()
})
it(`should init the proxy with owner and implementation`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with(
`0x${'00'.repeat(32)}`
)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ProxySequencerEntrypoint,
'init',
[OVM_SequencerEntrypoint.address, await wallet.getAddress()]
)
const ovmSSTOREs: any = Mock__OVM_ExecutionManager.smocked.ovmSSTORE.calls
expect(ovmSSTOREs[0]._key).to.equal(`0x${'00'.repeat(31)}01`)
expect(ovmSSTOREs[0]._value).to.equal(
addrToBytes32(await wallet.getAddress())
)
expect(ovmSSTOREs[1]._key).to.equal(`0x${'00'.repeat(32)}`)
expect(ovmSSTOREs[1]._value).to.equal(
addrToBytes32(OVM_SequencerEntrypoint.address)
)
// expect(await OVM_ProxySequencerEntrypoint.implementation()).to.equal(
// OVM_SequencerEntrypoint.address
// )
})
it(`should revert if proxy has already been inited`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with(
addrToBytes32(await wallet.getAddress())
)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ProxySequencerEntrypoint,
'init',
[ZERO_ADDRESS, ZERO_ADDRESS]
)
const ovmREVERT: any = Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal(
'ProxySequencerEntrypoint has already been inited'
)
})
it(`should allow owner to upgrade Entrypoint`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with(
addrToBytes32(await wallet.getAddress())
)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ProxySequencerEntrypoint,
'upgrade',
[`0x${'12'.repeat(20)}`]
)
const ovmSSTORE: any = Mock__OVM_ExecutionManager.smocked.ovmSSTORE.calls[0]
expect(ovmSSTORE._key).to.equal(`0x${'00'.repeat(32)}`)
expect(ovmSSTORE._value).to.equal(addrToBytes32(`0x${'12'.repeat(20)}`))
})
it(`should revert if non-owner tries to upgrade Entrypoint`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with(
`0x${'00'.repeat(32)}`
)
await callPrecompile(
Helper_PrecompileCaller,
OVM_ProxySequencerEntrypoint,
'upgrade',
[`0x${'12'.repeat(20)}`]
)
const ovmREVERT: any = Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal(
'Only owner can upgrade the Entrypoint'
)
})
it(`successfully calls ovmCREATEEOA through Entrypoint fallback`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with(
addrToBytes32(OVM_SequencerEntrypoint.address)
)
Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.will.return.with([
true,
'0x',
])
const calldata = '0xdeadbeef'
await Helper_PrecompileCaller.callPrecompile(
OVM_ProxySequencerEntrypoint.address,
calldata
)
const ovmDELEGATECALL: any =
Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.calls[0]
expect(ovmDELEGATECALL._address).to.equal(OVM_SequencerEntrypoint.address)
expect(ovmDELEGATECALL._calldata).to.equal(calldata)
})
})
import { expect } from '../../../setup'
/* External Imports */
import { waffle, ethers } from '@nomiclabs/buidler'
import { ContractFactory, Wallet, Contract } from 'ethers'
import { zeroPad } from '@ethersproject/bytes'
import { getContractInterface } from '../../../../src'
import {
encodeSequencerCalldata,
EIP155Transaction,
signNativeTransaction,
signEthSignMessage,
DEFAULT_EIP155_TX,
serializeNativeTransaction,
serializeEthSignTransaction,
ZERO_ADDRESS,
} from '../../../helpers'
import { smockit, MockContract } from '@eth-optimism/smock'
import { create } from 'lodash'
describe('OVM_SequencerEntrypoint', () => {
let wallet: Wallet
before(async () => {
const provider = waffle.provider
;[wallet] = provider.getWallets()
})
let Mock__OVM_ExecutionManager: MockContract
let Helper_PrecompileCaller: Contract
before(async () => {
Mock__OVM_ExecutionManager = smockit(
await ethers.getContractFactory('OVM_ExecutionManager')
)
Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420)
Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with([true, '0x'])
Helper_PrecompileCaller = await (
await ethers.getContractFactory('Helper_PrecompileCaller')
).deploy()
Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address)
})
let OVM_SequencerEntrypointFactory: ContractFactory
before(async () => {
OVM_SequencerEntrypointFactory = await ethers.getContractFactory(
'OVM_SequencerEntrypoint'
)
})
let OVM_SequencerEntrypoint: Contract
beforeEach(async () => {
OVM_SequencerEntrypoint = await OVM_SequencerEntrypointFactory.deploy()
Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(1)
Mock__OVM_ExecutionManager.smocked.ovmREVERT.will.revert()
})
describe('fallback()', async () => {
it('should call EIP155 if the transaction type is 0', async () => {
const calldata = await encodeSequencerCalldata(
wallet,
DEFAULT_EIP155_TX,
0
)
await Helper_PrecompileCaller.callPrecompile(
OVM_SequencerEntrypoint.address,
calldata
)
const encodedTx = serializeNativeTransaction(DEFAULT_EIP155_TX)
const sig = await signNativeTransaction(wallet, DEFAULT_EIP155_TX)
const expectedEOACalldata = getContractInterface(
'OVM_ECDSAContractAccount'
).encodeFunctionData('execute', [
encodedTx,
0, //isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
])
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0]
expect(ovmCALL._address).to.equal(await wallet.getAddress())
expect(ovmCALL._calldata).to.equal(expectedEOACalldata)
})
it('should send correct calldata if tx is a create and the transaction type is 0', async () => {
const createTx = { ...DEFAULT_EIP155_TX, to: '' }
const calldata = await encodeSequencerCalldata(wallet, createTx, 0)
await Helper_PrecompileCaller.callPrecompile(
OVM_SequencerEntrypoint.address,
calldata
)
const encodedTx = serializeNativeTransaction(createTx)
const sig = await signNativeTransaction(wallet, createTx)
const expectedEOACalldata = getContractInterface(
'OVM_ECDSAContractAccount'
).encodeFunctionData('execute', [
encodedTx,
0, //isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
])
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0]
expect(ovmCALL._address).to.equal(await wallet.getAddress())
expect(ovmCALL._calldata).to.equal(expectedEOACalldata)
})
for (let i = 0; i < 3; i += 2) {
it(`should call ovmCreateEOA when tx type is ${i} and ovmEXTCODESIZE returns 0`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(0)
const calldata = await encodeSequencerCalldata(
wallet,
DEFAULT_EIP155_TX,
i
)
await Helper_PrecompileCaller.callPrecompile(
OVM_SequencerEntrypoint.address,
calldata
)
const call: any =
Mock__OVM_ExecutionManager.smocked.ovmCREATEEOA.calls[0]
const eoaAddress = ethers.utils.recoverAddress(call._messageHash, {
v: call._v + 27,
r: call._r,
s: call._s,
})
expect(eoaAddress).to.equal(await wallet.getAddress())
})
}
it('should submit ETHSignedTypedData if TransactionType is 2', async () => {
const calldata = await encodeSequencerCalldata(
wallet,
DEFAULT_EIP155_TX,
2
)
await Helper_PrecompileCaller.callPrecompile(
OVM_SequencerEntrypoint.address,
calldata
)
const encodedTx = serializeEthSignTransaction(DEFAULT_EIP155_TX)
const sig = await signEthSignMessage(wallet, DEFAULT_EIP155_TX)
const expectedEOACalldata = getContractInterface(
'OVM_ECDSAContractAccount'
).encodeFunctionData('execute', [
encodedTx,
1, //isEthSignedMessage
`0x${sig.v}`, //v
`0x${sig.r}`, //r
`0x${sig.s}`, //s
])
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0]
expect(ovmCALL._address).to.equal(await wallet.getAddress())
expect(ovmCALL._calldata).to.equal(expectedEOACalldata)
})
// TODO: These tests should pass when smock is updated to >=0.1.0
it.skip('should revert if TransactionType is >2', async () => {
const calldata = '0x03'
await expect(
Helper_PrecompileCaller.callPrecompile(
OVM_SequencerEntrypoint.address,
calldata
)
).to.be.reverted
})
it.skip('should revert if TransactionType is 1', async () => {
const calldata = '0x01'
await expect(
Helper_PrecompileCaller.callPrecompile(
OVM_SequencerEntrypoint.address,
calldata
)
).to.be.reverted
})
})
})
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
import { expect } from '../../../setup' import { expect } from '../../../setup'
describe('Lib_OVMCodec', () => { /* Internal Imports */
describe('decodeEOATransaction', () => { import { Lib_OVMCodec_TEST_JSON } from '../../../data'
describe('when given a valid RLP-encoded transaction', () => { import { runJsonTest, toHexString } from '../../../helpers'
it('should return the decoded transaction struct', async () => {})
})
})
describe('encodeTransaction', () => { describe('Lib_OVMCodec', () => {
it('should ABI encode (packed) the given transaction', async () => {}) describe('JSON tests', () => {
}) runJsonTest('TestLib_OVMCodec', Lib_OVMCodec_TEST_JSON)
describe('hashTransaction', () => {
it('should return the hash of the encoded transaction', async () => {})
}) })
}) })
...@@ -4,5 +4,6 @@ export { tests as Lib_Bytes32Utils_TEST_JSON } from './json/libraries/utils/Lib_ ...@@ -4,5 +4,6 @@ export { tests as Lib_Bytes32Utils_TEST_JSON } from './json/libraries/utils/Lib_
export { tests as Lib_BytesUtils_TEST_JSON } from './json/libraries/utils/Lib_BytesUtils.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_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 Lib_MerkleTrie_TEST_JSON } from './json/libraries/trie/Lib_MerkleTrie.test.json'
export { tests as Lib_OVMCodec_TEST_JSON } from './json/libraries/codec/Lib_OVMCodec.test.json'
export { tests as CREATE2_TEST_JSON } from './json/create2.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' export { tests as SAFETY_CHECKER_TEST_JSON } from './json/safety-checker.test.json'
{
"tests": {
"decompressEIP155Transaction": {
"decompression": {
"in": [
"0x0001f4000064000064121212121212121212121212121212121212121299999999999999999999"
],
"out": [
[
100,
100000000,
500,
"0x1212121212121212121212121212121212121212",
0,
"0x99999999999999999999",
420
]
]
}
}
}
}
...@@ -5,10 +5,9 @@ ...@@ -5,10 +5,9 @@
"in": [ "in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false, false,
"0xfc", "0x01",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3"
"0x6c"
], ],
"out": [ "out": [
"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff" "0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff"
...@@ -18,10 +17,9 @@ ...@@ -18,10 +17,9 @@
"in": [ "in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false, false,
"0x00", "0xfc",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3"
"0x6c"
], ],
"out": [ "out": [
"0x0000000000000000000000000000000000000000" "0x0000000000000000000000000000000000000000"
...@@ -31,10 +29,9 @@ ...@@ -31,10 +29,9 @@
"in": [ "in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false, false,
"0xfc", "0x01",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3"
"0x6c"
], ],
"out": [ "out": [
"0x0000000000000000000000000000000000000000" "0x0000000000000000000000000000000000000000"
...@@ -44,23 +41,9 @@ ...@@ -44,23 +41,9 @@
"in": [ "in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false, false,
"0xfc", "0x01",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x6c"
],
"out": [
"0x0000000000000000000000000000000000000000"
]
},
"standard hash, invalid chainid parameter": {
"in": [
"0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080",
false,
"0xfc",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", "0x0000000000000000000000000000000000000000000000000000000000000000"
"0x00"
], ],
"out": [ "out": [
"0x0000000000000000000000000000000000000000" "0x0000000000000000000000000000000000000000"
...@@ -70,10 +53,9 @@ ...@@ -70,10 +53,9 @@
"in": [ "in": [
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
false, false,
"0xfc", "0x01",
"0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a",
"0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3"
"0x6c"
], ],
"out": [ "out": [
"0x1397890fcfC2D7563Aa01cE93A217b9809D3447B" "0x1397890fcfC2D7563Aa01cE93A217b9809D3447B"
...@@ -83,10 +65,9 @@ ...@@ -83,10 +65,9 @@
"in": [ "in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true, true,
"0x1c", "0x01",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98"
"0x6c"
], ],
"out": [ "out": [
"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff" "0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff"
...@@ -96,10 +77,9 @@ ...@@ -96,10 +77,9 @@
"in": [ "in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true, true,
"0x00", "0x1c",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98"
"0x6c"
], ],
"out": [ "out": [
"0x0000000000000000000000000000000000000000" "0x0000000000000000000000000000000000000000"
...@@ -109,10 +89,9 @@ ...@@ -109,10 +89,9 @@
"in": [ "in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true, true,
"0x1c", "0x01",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98"
"0x6c"
], ],
"out": [ "out": [
"0x0000000000000000000000000000000000000000" "0x0000000000000000000000000000000000000000"
...@@ -122,10 +101,9 @@ ...@@ -122,10 +101,9 @@
"in": [ "in": [
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
true, true,
"0x1c", "0x01",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000"
"0x6c"
], ],
"out": [ "out": [
"0x0000000000000000000000000000000000000000" "0x0000000000000000000000000000000000000000"
...@@ -135,10 +113,9 @@ ...@@ -135,10 +113,9 @@
"in": [ "in": [
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
true, true,
"0x1c", "0x01",
"0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a",
"0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98"
"0x6c"
], ],
"out": [ "out": [
"0xf8bd1421f9D28C8F58f33B25a6faB16F3f89b749" "0xf8bd1421f9D28C8F58f33B25a6faB16F3f89b749"
......
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { zeroPad } from '@ethersproject/bytes'
import { Wallet } from 'ethers'
import {
remove0x,
numberToHexString,
hexStrToBuf,
makeAddressManager,
} from '../'
import { ZERO_ADDRESS } from '../constants'
export interface EIP155Transaction {
nonce: number
gasLimit: number
gasPrice: number
to: string
data: string
chainId: number
}
export interface SignatureParameters {
messageHash: string
v: string
r: string
s: string
}
export const DEFAULT_EIP155_TX: EIP155Transaction = {
to: `0x${'12'.repeat(20)}`,
nonce: 100,
gasLimit: 1000000,
gasPrice: 100000000,
data: `0x${'99'.repeat(10)}`,
chainId: 420,
}
export const getRawSignedComponents = (signed: string): any[] => {
return [signed.slice(130, 132), signed.slice(2, 66), signed.slice(66, 130)]
}
export const getSignedComponents = (signed: string): any[] => {
return ethers.utils.RLP.decode(signed).slice(-3)
}
export const encodeCompactTransaction = (transaction: any): string => {
const nonce = zeroPad(transaction.nonce, 3)
const gasLimit = zeroPad(transaction.gasLimit, 3)
if (transaction.gasPrice % 1000000 !== 0)
throw Error('gas price must be a multiple of 1000000')
const compressedGasPrice: any = transaction.gasPrice / 1000000
const gasPrice = zeroPad(compressedGasPrice, 3)
const to = !transaction.to.length
? hexStrToBuf(ZERO_ADDRESS)
: hexStrToBuf(transaction.to)
const data = hexStrToBuf(transaction.data)
return Buffer.concat([
Buffer.from(gasLimit),
Buffer.from(gasPrice),
Buffer.from(nonce),
Buffer.from(to),
data,
]).toString('hex')
}
export const serializeEthSignTransaction = (
transaction: EIP155Transaction
): string => {
return ethers.utils.defaultAbiCoder.encode(
['uint256', 'uint256', 'uint256', 'uint256', 'address', 'bytes'],
[
transaction.nonce,
transaction.gasLimit,
transaction.gasPrice,
transaction.chainId,
transaction.to,
transaction.data,
]
)
}
export const serializeNativeTransaction = (
transaction: EIP155Transaction
): string => {
return ethers.utils.serializeTransaction(transaction)
}
export const signEthSignMessage = async (
wallet: Wallet,
transaction: EIP155Transaction
): Promise<SignatureParameters> => {
const serializedTransaction = serializeEthSignTransaction(transaction)
const transactionHash = ethers.utils.keccak256(serializedTransaction)
const transactionHashBytes = ethers.utils.arrayify(transactionHash)
const transactionSignature = await wallet.signMessage(transactionHashBytes)
const messageHash = ethers.utils.hashMessage(transactionHashBytes)
let [v, r, s] = getRawSignedComponents(transactionSignature).map(
(component) => {
return remove0x(component)
}
)
v = '0' + (parseInt(v, 16) - 27)
return {
messageHash,
v,
r,
s,
}
}
export const signNativeTransaction = async (
wallet: Wallet,
transaction: EIP155Transaction
): Promise<SignatureParameters> => {
const serializedTransaction = serializeNativeTransaction(transaction)
const transactionSignature = await wallet.signTransaction(transaction)
const messageHash = ethers.utils.keccak256(serializedTransaction)
let [v, r, s] = getSignedComponents(transactionSignature).map((component) => {
return remove0x(component)
})
v = '0' + (parseInt(v, 16) - 420 * 2 - 8 - 27)
return {
messageHash,
v,
r,
s,
}
}
export const signTransaction = async (
wallet: Wallet,
transaction: EIP155Transaction,
transactionType: number
): Promise<SignatureParameters> => {
return transactionType === 2
? signEthSignMessage(wallet, transaction) //ETH Signed tx
: signNativeTransaction(wallet, transaction) //Create EOA tx or EIP155 tx
}
export const encodeSequencerCalldata = async (
wallet: Wallet,
transaction: EIP155Transaction,
transactionType: number
) => {
const sig = await signTransaction(wallet, transaction, transactionType)
const encodedTransaction = encodeCompactTransaction(transaction)
const dataPrefix = `0x0${transactionType}${sig.r}${sig.s}${sig.v}`
const calldata =
transactionType === 1
? `${dataPrefix}${remove0x(sig.messageHash)}` // Create EOA tx
: `${dataPrefix}${encodedTransaction}` // EIP155 tx or ETH Signed Tx
return calldata
}
export * from './revert-flags' export * from './revert-flags'
export * from './encoding'
...@@ -4,6 +4,18 @@ import { expect } from '../../setup' ...@@ -4,6 +4,18 @@ import { expect } from '../../setup'
import { ethers } from '@nomiclabs/buidler' import { ethers } from '@nomiclabs/buidler'
import { Contract, BigNumber } from 'ethers' import { Contract, BigNumber } from 'ethers'
const bigNumberify = (arr) => {
return arr.map((el: any) => {
if (typeof el === 'number') {
return BigNumber.from(el)
} else if (Array.isArray(el)) {
return bigNumberify(el)
} else {
return el
}
})
}
export const runJsonTest = (contractName: string, json: any): void => { export const runJsonTest = (contractName: string, json: any): void => {
let contract: Contract let contract: Contract
before(async () => { before(async () => {
...@@ -20,15 +32,7 @@ export const runJsonTest = (contractName: string, json: any): void => { ...@@ -20,15 +32,7 @@ export const runJsonTest = (contractName: string, json: any): void => {
} else { } else {
expect( expect(
await contract.functions[functionName](...test.in) await contract.functions[functionName](...test.in)
).to.deep.equal( ).to.deep.equal(bigNumberify(test.out))
test.out.map((out: any) => {
if (typeof out === 'number') {
return BigNumber.from(out)
} else {
return out
}
})
)
} }
}) })
} }
......
...@@ -20,6 +20,7 @@ import { ...@@ -20,6 +20,7 @@ import {
isTestStep_CALL, isTestStep_CALL,
isTestStep_CREATE, isTestStep_CREATE,
isTestStep_CREATE2, isTestStep_CREATE2,
isTestStep_CREATEEOA,
isTestStep_Context, isTestStep_Context,
isTestStep_evm, isTestStep_evm,
isTestStep_Run, isTestStep_Run,
...@@ -27,6 +28,7 @@ import { ...@@ -27,6 +28,7 @@ import {
isTestStep_EXTCODEHASH, isTestStep_EXTCODEHASH,
isTestStep_EXTCODECOPY, isTestStep_EXTCODECOPY,
isTestStep_REVERT, isTestStep_REVERT,
isTestStep_SETNONCE,
} from './test.types' } from './test.types'
import { encodeRevertData, REVERT_FLAGS } from '../codec' import { encodeRevertData, REVERT_FLAGS } from '../codec'
import { import {
...@@ -333,9 +335,11 @@ export class ExecutionManagerTestRunner { ...@@ -333,9 +335,11 @@ export class ExecutionManagerTestRunner {
if ( if (
isTestStep_SSTORE(step) || isTestStep_SSTORE(step) ||
isTestStep_SLOAD(step) || isTestStep_SLOAD(step) ||
isTestStep_SETNONCE(step) ||
isTestStep_EXTCODESIZE(step) || isTestStep_EXTCODESIZE(step) ||
isTestStep_EXTCODEHASH(step) || isTestStep_EXTCODEHASH(step) ||
isTestStep_EXTCODECOPY(step) isTestStep_EXTCODECOPY(step) ||
isTestStep_CREATEEOA(step)
) { ) {
functionParams = Object.values(step.functionParams) functionParams = Object.values(step.functionParams)
} else if (isTestStep_CALL(step)) { } else if (isTestStep_CALL(step)) {
......
...@@ -10,6 +10,7 @@ export type ContextOpcode = ...@@ -10,6 +10,7 @@ export type ContextOpcode =
| 'ovmTIMESTAMP' | 'ovmTIMESTAMP'
| 'ovmGASLIMIT' | 'ovmGASLIMIT'
| 'ovmCHAINID' | 'ovmCHAINID'
| 'ovmGETNONCE'
type CallOpcode = 'ovmCALL' | 'ovmSTATICCALL' | 'ovmDELEGATECALL' type CallOpcode = 'ovmCALL' | 'ovmSTATICCALL' | 'ovmDELEGATECALL'
...@@ -85,6 +86,15 @@ interface TestStep_SLOAD { ...@@ -85,6 +86,15 @@ interface TestStep_SLOAD {
expectedReturnValue: string | RevertFlagError expectedReturnValue: string | RevertFlagError
} }
interface TestStep_SETNONCE {
functionName: 'ovmSETNONCE'
functionParams: {
_nonce: string
}
expectedReturnStatus: boolean
expectedReturnValue?: RevertFlagError
}
export interface TestStep_CALL { export interface TestStep_CALL {
functionName: CallOpcode functionName: CallOpcode
functionParams: { functionParams: {
...@@ -121,6 +131,18 @@ interface TestStep_CREATE2 { ...@@ -121,6 +131,18 @@ interface TestStep_CREATE2 {
expectedReturnValue: string | RevertFlagError expectedReturnValue: string | RevertFlagError
} }
interface TestStep_CREATEEOA {
functionName: 'ovmCREATEEOA'
functionParams: {
_messageHash: string
_v: number
_r: string
_s: string
}
expectedReturnStatus: boolean
expectedReturnValue: string | RevertFlagError
}
export interface TestStep_Run { export interface TestStep_Run {
functionName: 'run' functionName: 'run'
functionParams: { functionParams: {
...@@ -139,9 +161,11 @@ export type TestStep = ...@@ -139,9 +161,11 @@ export type TestStep =
| TestStep_Context | TestStep_Context
| TestStep_SSTORE | TestStep_SSTORE
| TestStep_SLOAD | TestStep_SLOAD
| TestStep_SETNONCE
| TestStep_CALL | TestStep_CALL
| TestStep_CREATE | TestStep_CREATE
| TestStep_CREATE2 | TestStep_CREATE2
| TestStep_CREATEEOA
| TestStep_EXTCODESIZE | TestStep_EXTCODESIZE
| TestStep_EXTCODEHASH | TestStep_EXTCODEHASH
| TestStep_EXTCODECOPY | TestStep_EXTCODECOPY
...@@ -182,6 +206,7 @@ export const isTestStep_Context = ( ...@@ -182,6 +206,7 @@ export const isTestStep_Context = (
'ovmGASLIMIT', 'ovmGASLIMIT',
'ovmCHAINID', 'ovmCHAINID',
'ovmL1QUEUEORIGIN', 'ovmL1QUEUEORIGIN',
'ovmGETNONCE',
].includes(step.functionName) ].includes(step.functionName)
} }
...@@ -193,6 +218,12 @@ export const isTestStep_SLOAD = (step: TestStep): step is TestStep_SLOAD => { ...@@ -193,6 +218,12 @@ export const isTestStep_SLOAD = (step: TestStep): step is TestStep_SLOAD => {
return step.functionName === 'ovmSLOAD' return step.functionName === 'ovmSLOAD'
} }
export const isTestStep_SETNONCE = (
step: TestStep
): step is TestStep_SETNONCE => {
return step.functionName === 'ovmSETNONCE'
}
export const isTestStep_EXTCODESIZE = ( export const isTestStep_EXTCODESIZE = (
step: TestStep step: TestStep
): step is TestStep_EXTCODESIZE => { ): step is TestStep_EXTCODESIZE => {
...@@ -225,6 +256,12 @@ export const isTestStep_CREATE = (step: TestStep): step is TestStep_CREATE => { ...@@ -225,6 +256,12 @@ export const isTestStep_CREATE = (step: TestStep): step is TestStep_CREATE => {
return step.functionName === 'ovmCREATE' return step.functionName === 'ovmCREATE'
} }
export const isTestStep_CREATEEOA = (
step: TestStep
): step is TestStep_CREATEEOA => {
return step.functionName === 'ovmCREATEEOA'
}
export const isTestStep_CREATE2 = ( export const isTestStep_CREATE2 = (
step: TestStep step: TestStep
): step is TestStep_CREATE2 => { ): step is TestStep_CREATE2 => {
......
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