Commit 8575b629 authored by Kelvin Fichter's avatar Kelvin Fichter

Added latest tests

parent 10667c03
...@@ -86,6 +86,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -86,6 +86,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
); );
} }
/**
* Makes sure we're not inside a static context.
*/
modifier notStatic() {
if (messageContext.isStatic == true) {
_revertWithFlag(RevertFlag.STATIC_VIOLATION);
}
_;
}
/************************************ /************************************
* Transaction Execution Entrypoint * * Transaction Execution Entrypoint *
...@@ -263,6 +273,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -263,6 +273,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
) )
override override
public public
notStatic
netGasCost(40000 + _bytecode.length * 100) netGasCost(40000 + _bytecode.length * 100)
returns ( returns (
address _contract address _contract
...@@ -277,12 +288,10 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -277,12 +288,10 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
_getAccountNonce(creator) _getAccountNonce(creator)
); );
_createContract( return _createContract(
contractAddress, contractAddress,
_bytecode _bytecode
); );
return contractAddress;
} }
/** /**
...@@ -297,6 +306,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -297,6 +306,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
) )
override override
public public
notStatic
netGasCost(40000 + _bytecode.length * 100) netGasCost(40000 + _bytecode.length * 100)
returns ( returns (
address _contract address _contract
...@@ -312,12 +322,10 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -312,12 +322,10 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
_salt _salt
); );
_createContract( return _createContract(
contractAddress, contractAddress,
_bytecode _bytecode
); );
return contractAddress;
} }
...@@ -375,6 +383,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -375,6 +383,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
) )
override override
public public
notStatic
{ {
// Recover the EOA address from the message hash and signature parameters. Since we do the // Recover the EOA address from the message hash and signature parameters. Since we do the
// hashing in advance, we don't have handle different message hashing schemes. Even if this // hashing in advance, we don't have handle different message hashing schemes. Even if this
...@@ -445,12 +454,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -445,12 +454,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
MessageContext memory nextMessageContext = messageContext; MessageContext memory nextMessageContext = messageContext;
nextMessageContext.ovmCALLER = nextMessageContext.ovmADDRESS; nextMessageContext.ovmCALLER = nextMessageContext.ovmADDRESS;
nextMessageContext.ovmADDRESS = _address; nextMessageContext.ovmADDRESS = _address;
nextMessageContext.isCreation = false;
bool isStaticEntrypoint = false;
return _callContract( return _callContract(
nextMessageContext, nextMessageContext,
_gasLimit, _gasLimit,
_address, _address,
_calldata _calldata,
isStaticEntrypoint
); );
} }
...@@ -480,12 +492,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -480,12 +492,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
nextMessageContext.ovmCALLER = nextMessageContext.ovmADDRESS; nextMessageContext.ovmCALLER = nextMessageContext.ovmADDRESS;
nextMessageContext.ovmADDRESS = _address; nextMessageContext.ovmADDRESS = _address;
nextMessageContext.isStatic = true; nextMessageContext.isStatic = true;
nextMessageContext.isCreation = false;
bool isStaticEntrypoint = true;
return _callContract( return _callContract(
nextMessageContext, nextMessageContext,
_gasLimit, _gasLimit,
_address, _address,
_calldata _calldata,
isStaticEntrypoint
); );
} }
...@@ -512,12 +527,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -512,12 +527,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
{ {
// DELEGATECALL does not change anything about the message context. // DELEGATECALL does not change anything about the message context.
MessageContext memory nextMessageContext = messageContext; MessageContext memory nextMessageContext = messageContext;
nextMessageContext.isCreation = false;
bool isStaticEntrypoint = false;
return _callContract( return _callContract(
nextMessageContext, nextMessageContext,
_gasLimit, _gasLimit,
_address, _address,
_calldata _calldata,
isStaticEntrypoint
); );
} }
...@@ -561,6 +579,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -561,6 +579,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
) )
override override
public public
notStatic
netGasCost(60000) netGasCost(60000)
{ {
// We always SSTORE to the storage of ADDRESS. // We always SSTORE to the storage of ADDRESS.
...@@ -694,12 +713,27 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -694,12 +713,27 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// 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(_address); _initPendingAccount(_address);
// We're going into a contract creation, so we need to set this flag to get the correct
// revert behavior.
messageContext.isCreation = true;
// Actually deploy the contract and retrieve its address. This step is hiding a lot of // Actually deploy the contract and retrieve its address. This step is hiding a lot of
// complexity because we need to ensure that contract creation *never* reverts by itself. // complexity because we need to ensure that contract creation *never* reverts by itself.
// We cover this partially by storing a revert flag and returning (instead of reverting) // We cover this partially by storing a revert flag and returning (instead of reverting)
// when we know that we're inside a contract's creation code. // when we know that we're inside a contract's creation code.
address ethAddress = Lib_EthUtils.createContract(_bytecode); address ethAddress = Lib_EthUtils.createContract(_bytecode);
// Now reset this flag so we go back to normal revert behavior.
messageContext.isCreation = true;
// Contract creation returns the zero address when it fails, which should only be possible
// if the user intentionally runs out of gas. However, we might still have a bit of gas
// left over since contract calls can only be passed 63/64ths of total gas, so we need to
// explicitly handle this case here.
if (ethAddress == address(0)) {
_revertWithFlag(RevertFlag.CREATE_EXCEPTION);
}
// Here we pull out the revert flag that would've been set during creation code. Now that // Here we pull out the revert flag that would've been set during creation code. Now that
// we're out of creation code again, we can just revert normally while passing the flag // we're out of creation code again, we can just revert normally while passing the flag
// through the revert data. // through the revert data.
...@@ -732,12 +766,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -732,12 +766,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
* Creates a new contract and associates it with some contract address. * Creates a new contract and associates it with some contract address.
* @param _contractAddress Address to associate the created contract with. * @param _contractAddress Address to associate the created contract with.
* @param _bytecode Bytecode to be used to create the contract. * @param _bytecode Bytecode to be used to create the contract.
* @return _created Final OVM contract address.
*/ */
function _createContract( function _createContract(
address _contractAddress, address _contractAddress,
bytes memory _bytecode bytes memory _bytecode
) )
internal internal
returns (
address _created
)
{ {
// We always update the nonce of the creating account, even if the creation fails. // We always update the nonce of the creating account, even if the creation fails.
_setAccountNonce(ovmADDRESS(), 1); _setAccountNonce(ovmADDRESS(), 1);
...@@ -750,7 +788,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -750,7 +788,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// Run `safeCREATE` in a new EVM message so that our changes can be reflected even if // Run `safeCREATE` in a new EVM message so that our changes can be reflected even if
// `safeCREATE` reverts. // `safeCREATE` reverts.
_handleExternalInteraction( (bool _success, ) = _handleExternalInteraction(
nextMessageContext, nextMessageContext,
gasleft(), gasleft(),
address(this), address(this),
...@@ -758,12 +796,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -758,12 +796,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
"safeCREATE(address,bytes)", "safeCREATE(address,bytes)",
_contractAddress, _contractAddress,
_bytecode _bytecode
) ),
false
); );
// Need to make sure that this flag is reset so that it isn't propagated to creations in // Need to make sure that this flag is reset so that it isn't propagated to creations in
// some parent EVM message. // some parent EVM message.
messageRecord.revertFlag = RevertFlag.DID_NOT_REVERT; messageRecord.revertFlag = RevertFlag.DID_NOT_REVERT;
// Yellow paper requires that address returned is zero if the contract deployment fails.
return _success ? _contractAddress : address(0);
} }
/** /**
...@@ -772,6 +814,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -772,6 +814,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
* @param _gasLimit Amount of gas to be passed into this call. * @param _gasLimit Amount of gas to be passed into this call.
* @param _contract Address used to resolve the deployed contract. * @param _contract Address used to resolve the deployed contract.
* @param _calldata Data to send along with the call. * @param _calldata Data to send along with the call.
* @param _isStaticEntrypoint Whether or not this is coming from ovmSTATICCALL.
* @return _success Whether or not the call returned (rather than reverted). * @return _success Whether or not the call returned (rather than reverted).
* @return _returndata Data returned by the call. * @return _returndata Data returned by the call.
*/ */
...@@ -779,7 +822,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -779,7 +822,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
MessageContext memory _nextMessageContext, MessageContext memory _nextMessageContext,
uint256 _gasLimit, uint256 _gasLimit,
address _contract, address _contract,
bytes memory _calldata bytes memory _calldata,
bool _isStaticEntrypoint
) )
internal internal
returns ( returns (
...@@ -791,7 +835,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -791,7 +835,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
_nextMessageContext, _nextMessageContext,
_gasLimit, _gasLimit,
_getAccountEthAddress(_contract), _getAccountEthAddress(_contract),
_calldata _calldata,
_isStaticEntrypoint
); );
} }
...@@ -801,6 +846,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -801,6 +846,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
* @param _gasLimit Amount of gas to be passed into this call. * @param _gasLimit Amount of gas to be passed into this call.
* @param _target Address of the contract to call. * @param _target Address of the contract to call.
* @param _data Data to send along with the call. * @param _data Data to send along with the call.
* @param _isStaticEntrypoint Whether or not this is coming from ovmSTATICCALL.
* @return _success Whether or not the call returned (rather than reverted). * @return _success Whether or not the call returned (rather than reverted).
* @return _returndata Data returned by the call. * @return _returndata Data returned by the call.
*/ */
...@@ -808,7 +854,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -808,7 +854,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
MessageContext memory _nextMessageContext, MessageContext memory _nextMessageContext,
uint256 _gasLimit, uint256 _gasLimit,
address _target, address _target,
bytes memory _data bytes memory _data,
bool _isStaticEntrypoint
) )
internal internal
returns ( returns (
...@@ -835,6 +882,9 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -835,6 +882,9 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// existence. // existence.
(bool success, bytes memory returndata) = _target.call{gas: _gasLimit}(_data); (bool success, bytes memory returndata) = _target.call{gas: _gasLimit}(_data);
// Switch back to the original message context now that we're out of the call.
_switchMessageContext(_nextMessageContext, prevMessageContext);
// Assuming there were no reverts, the message record should be accurate here. We'll update // Assuming there were no reverts, the message record should be accurate here. We'll update
// this value in the case of a revert. // this value in the case of a revert.
uint256 nuisanceGasLeft = messageRecord.nuisanceGasLeft; uint256 nuisanceGasLeft = messageRecord.nuisanceGasLeft;
...@@ -846,6 +896,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -846,6 +896,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
RevertFlag flag, RevertFlag flag,
uint256 nuisanceGasLeftPostRevert, uint256 nuisanceGasLeftPostRevert,
uint256 ovmGasRefund, uint256 ovmGasRefund,
bytes memory returndataFromFlag
) = _decodeRevertData(returndata); ) = _decodeRevertData(returndata);
// INVALID_STATE_ACCESS is the only flag that triggers an immediate abort of the // INVALID_STATE_ACCESS is the only flag that triggers an immediate abort of the
...@@ -855,6 +906,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -855,6 +906,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
_revertWithFlag(flag); _revertWithFlag(flag);
} }
// STATIC_VIOLATION should be passed all the way back up to the previous ovmSTATICCALL
// in the call stack.
if (
flag == RevertFlag.STATIC_VIOLATION
&& _isStaticEntrypoint == false
) {
_revertWithFlag(flag);
}
// INTENTIONAL_REVERT and UNSAFE_BYTECODE aren't dependent on the input state, so we // INTENTIONAL_REVERT and UNSAFE_BYTECODE aren't dependent on the input state, so we
// can just handle them like standard reverts. Our only change here is to record the // can just handle them like standard reverts. Our only change here is to record the
// gas refund reported by the call (enforced by safety checking). // gas refund reported by the call (enforced by safety checking).
...@@ -864,7 +924,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -864,7 +924,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
) { ) {
transactionRecord.ovmGasRefund = ovmGasRefund; transactionRecord.ovmGasRefund = ovmGasRefund;
} }
// INTENTIONAL_REVERT needs to pass up the user-provided return data encoded into the
// flag, *not* the full encoded flag. All other revert types return no data.
if (flag == RevertFlag.INTENTIONAL_REVERT) {
returndata = returndataFromFlag;
} else {
returndata = hex'';
}
// Reverts mean we need to use up whatever "nuisance gas" was used by the call. // Reverts mean we need to use up whatever "nuisance gas" was used by the call.
// EXCEEDS_NUISANCE_GAS explicitly reduces the remaining nuisance gas for this message // EXCEEDS_NUISANCE_GAS explicitly reduces the remaining nuisance gas for this message
// to zero. OUT_OF_GAS is a "pseudo" flag given that messages return no data when they // to zero. OUT_OF_GAS is a "pseudo" flag given that messages return no data when they
...@@ -876,9 +944,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -876,9 +944,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// We need to reset the nuisance gas back to its original value minus the amount used here. // We need to reset the nuisance gas back to its original value minus the amount used here.
messageRecord.nuisanceGasLeft = prevNuisanceGasLeft - (nuisanceGasLimit - nuisanceGasLeft); messageRecord.nuisanceGasLeft = prevNuisanceGasLeft - (nuisanceGasLimit - nuisanceGasLeft);
// Switch back to the original message context now that we're out of the call.
_switchMessageContext(_nextMessageContext, prevMessageContext);
return ( return (
success, success,
returndata returndata
...@@ -1245,7 +1310,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -1245,7 +1310,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// *single* byte, something the OVM_ExecutionManager will not return in any other case. // *single* byte, something the OVM_ExecutionManager will not return in any other case.
// We're thereby allowed to communicate failure without allowing contracts to trick us into // We're thereby allowed to communicate failure without allowing contracts to trick us into
// thinking there was a failure. // thinking there was a failure.
if (_inCreationContext()) { if (messageContext.isCreation) {
messageRecord.revertFlag = _flag; messageRecord.revertFlag = _flag;
assembly { assembly {
...@@ -1523,6 +1588,11 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -1523,6 +1588,11 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
if (_prevMessageContext.isStatic != _nextMessageContext.isStatic) { if (_prevMessageContext.isStatic != _nextMessageContext.isStatic) {
messageContext.isStatic = _nextMessageContext.isStatic; messageContext.isStatic = _nextMessageContext.isStatic;
} }
// Avoid unnecessary the SSTORE.
if (_prevMessageContext.isCreation != _nextMessageContext.isCreation) {
messageContext.isCreation = _nextMessageContext.isCreation;
}
} }
/** /**
...@@ -1561,22 +1631,4 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -1561,22 +1631,4 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
messageRecord.nuisanceGasLeft = 0; messageRecord.nuisanceGasLeft = 0;
messageRecord.revertFlag = RevertFlag.DID_NOT_REVERT; messageRecord.revertFlag = RevertFlag.DID_NOT_REVERT;
} }
/**
* Checks whether we're inside contract creation code.
* @return _inCreation Whether or not we're in a contract creation.
*/
function _inCreationContext()
internal
returns (
bool _inCreation
)
{
// An interesting "hack" of sorts. Since the contract doesn't exist yet, it won't have any
// stored contract code. A simple-but-elegant way to detect this condition.
return (
ovmADDRESS() != address(0)
&& ovmEXTCODESIZE(ovmADDRESS()) == 0
);
}
} }
...@@ -228,6 +228,7 @@ contract OVM_StateManager is iOVM_StateManager { ...@@ -228,6 +228,7 @@ contract OVM_StateManager is iOVM_StateManager {
Lib_OVMCodec.Account storage account = accounts[_address]; Lib_OVMCodec.Account storage account = accounts[_address];
account.nonce = 1; account.nonce = 1;
account.codeHash = keccak256(hex'80'); account.codeHash = keccak256(hex'80');
account.isFresh = true;
} }
/** /**
...@@ -405,9 +406,9 @@ contract OVM_StateManager is iOVM_StateManager { ...@@ -405,9 +406,9 @@ contract OVM_StateManager is iOVM_StateManager {
bool _exists bool _exists
) )
{ {
return verifiedContractStorage[_contract][_key]; return verifiedContractStorage[_contract][_key] || accounts[_contract].isFresh;
} }
/** /**
* Checks whether a storage slot has already been retrieved, and marks it as retrieved if not. * Checks whether a storage slot has already been retrieved, and marks it as retrieved if not.
* @param _contract Address of the contract to check. * @param _contract Address of the contract to check.
......
...@@ -17,7 +17,9 @@ interface iOVM_ExecutionManager { ...@@ -17,7 +17,9 @@ interface iOVM_ExecutionManager {
EXCEEDS_NUISANCE_GAS, EXCEEDS_NUISANCE_GAS,
INVALID_STATE_ACCESS, INVALID_STATE_ACCESS,
UNSAFE_BYTECODE, UNSAFE_BYTECODE,
CREATE_COLLISION CREATE_COLLISION,
STATIC_VIOLATION,
CREATE_EXCEPTION
} }
enum GasMetadataKey { enum GasMetadataKey {
...@@ -65,6 +67,7 @@ interface iOVM_ExecutionManager { ...@@ -65,6 +67,7 @@ interface iOVM_ExecutionManager {
address ovmCALLER; address ovmCALLER;
address ovmADDRESS; address ovmADDRESS;
bool isStatic; bool isStatic;
bool isCreation;
} }
struct MessageRecord { struct MessageRecord {
......
...@@ -20,6 +20,7 @@ library Lib_OVMCodec { ...@@ -20,6 +20,7 @@ library Lib_OVMCodec {
bytes32 storageRoot; bytes32 storageRoot;
bytes32 codeHash; bytes32 codeHash;
address ethAddress; address ethAddress;
bool isFresh;
} }
struct EVMAccount { struct EVMAccount {
......
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
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
struct MessageSteps {
bytes[] callsToEM;
bool shouldRevert;
}
struct EMResponse {
bool success;
bytes data;
}
contract Helper_CodeContractForCalls {
function runSteps(
MessageSteps calldata _stepsToRun
) external returns(EMResponse[] memory) {
uint numSteps = _stepsToRun.callsToEM.length;
EMResponse[] memory EMResponses = new EMResponse[](numSteps);
for (uint i = 0; i < numSteps; i++) {
bytes memory dataToSend = _stepsToRun.callsToEM[i];
(bool success, bytes memory responseData) = address(msg.sender).call(dataToSend);
EMResponses[i].success = success;
EMResponses[i].data = responseData;
}
return EMResponses; // TODO: revert with this data in case of !shouldRevert
}
}
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
import {console} from "@nomiclabs/buidler/console.sol";
interface Helper_CodeContractDataTypes {
struct CALLResponse {
bool success;
bytes data;
}
}
contract Helper_CodeContractForCalls is Helper_CodeContractDataTypes {
bytes constant sampleCREATEData = abi.encodeWithSignature("ovmCREATE(bytes)", hex"");
bytes constant sampleCREATE2Data = abi.encodeWithSignature("ovmCREATE2(bytes,bytes32)", hex"", 0x4242424242424242424242424242424242424242424242424242424242424242);
function runSteps(
bytes[] memory callsToEM,
bool _shouldRevert,
address _createEMResponsesStorer
) public returns(CALLResponse[] memory) {
console.log("in runSteps()");
uint numSteps = callsToEM.length;
CALLResponse[] memory EMResponses = new CALLResponse[](numSteps);
for (uint i = 0; i < numSteps; i++) {
bytes memory dataToSend = callsToEM[i];
console.log("calling EM with data:");
console.logBytes(dataToSend);
(bool success, bytes memory responseData) = address(msg.sender).call(dataToSend);
console.log("step to EM had result:");
console.logBool(success);
EMResponses[i].success = success;
if (_isOVMCreateCall(dataToSend)) {
console.log("step to EM returned data:");
console.logBytes(responseData);
EMResponses[i].data = abi.encode(
responseData,
_getStoredEMREsponsesInCreate(_createEMResponsesStorer)
);
console.log("since this is create step, stored concatenation:");
console.logBytes(EMResponses[i].data);
} else {
console.log("step to EM returned data:");
console.logBytes(responseData);
EMResponses[i].data = responseData;
}
}
return EMResponses;
}
function _getStoredEMREsponsesInCreate(address _createEMResponsesStorer) internal returns(bytes memory) {
(bool success, bytes memory data) = _createEMResponsesStorer.call(abi.encodeWithSignature("getLastResponses()"));
return data;
}
function _isOVMCreateCall(bytes memory _calldata) public returns(bool) {
return (
_doMethodIdsMatch(_calldata, sampleCREATEData) || _doMethodIdsMatch(_calldata, sampleCREATE2Data)
);
}
function _doMethodIdsMatch(bytes memory _calldata1, bytes memory _calldata2) internal returns(bool) {
return (
_calldata1[0] == _calldata2[0] &&
_calldata1[1] == _calldata2[1] &&
_calldata1[2] == _calldata2[2] &&
_calldata1[3] == _calldata2[3]
);
}
}
contract Helper_CodeContractForCreates is Helper_CodeContractForCalls {
constructor(
bytes[] memory callsToEM,
bool _shouldRevert,
bytes memory _codeToDeploy,
address _createEMResponsesStorer
) {
console.log("In CREATE helper (deployment)");
CALLResponse[] memory responses = runSteps(callsToEM, _shouldRevert, _createEMResponsesStorer);
Helper_CreateEMResponsesStorer(_createEMResponsesStorer).store(responses);
uint lengthToDeploy = _codeToDeploy.length;
// todo revert if _shouldrevert
assembly {
return(add(_codeToDeploy, 0x20), lengthToDeploy)
}
}
}
contract Helper_CreateEMResponsesStorer is Helper_CodeContractDataTypes {
CALLResponse[] responses;
function store(
CALLResponse[] memory _responses
) public {
console.log("create storer helper is storing responses...");
for (uint i = 0; i < _responses.length; i++) {
responses.push();
responses[i] = _responses[i];
}
console.log("helper successfully stored this many responses:");
console.logUint(responses.length);
}
function getLastResponses() public returns(CALLResponse[] memory) {
console.log("helper is retreiving last stored responses. It has this many responses stored:");
console.logUint(responses.length);
CALLResponse[] memory toReturn = responses;
delete responses;
return toReturn;
}
}
contract Helper_CodeContractForReverts {
function doRevert(
bytes memory _revertdata
) public {
uint revertLength = _revertdata.length;
assembly {
revert(add(_revertdata, 0x20), revertLength)
}
}
}
// note: behavior of this contract covers all 3 cases of EVM message exceptions:
// - out of gas
// - INVALID opcode
// - invalid JUMPDEST
contract Helper_CodeContractForInvalid {
function doInvalid() public {
assembly {
invalid()
}
}
}
// note: behavior of this contract covers all 3 cases of EVM message exceptions:
// - out of gas
// - INVALID opcode
// - invalid JUMPDEST
contract Helper_CodeContractForInvalidInCreation {
constructor() public {
assembly {
invalid()
}
}
}
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
contract Helper_ModifiableStorage { contract Helper_ModifiableStorage {
address private target; mapping (address => address) private target;
constructor( constructor(
address _target address _target
) { ) {
target = _target; target[address(this)] = _target;
} }
fallback() fallback()
external external
{ {
(bool success, bytes memory returndata) = target.delegatecall(msg.data); (bool success, bytes memory returndata) = target[address(this)].delegatecall(msg.data);
if (success) { if (success) {
assembly { assembly {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"build": "yarn run build:contracts", "build": "yarn run build:contracts",
"build:contracts": "buidler compile", "build:contracts": "buidler compile",
"test": "yarn run test:contracts", "test": "yarn run test:contracts",
"test:contracts": "buidler test \"test/contracts/OVM/verification/OVM_FraudVerifier.spec.ts\"" "test:contracts": "buidler test \"test/contracts/OVM/execution/OVM_ExecutionManager/ovmSTATICCALL.spec.ts\""
}, },
"devDependencies": { "devDependencies": {
"@nomiclabs/buidler": "^1.4.4", "@nomiclabs/buidler": "^1.4.4",
......
/* Internal Imports */
import {
runExecutionManagerTest,
TestDefinition,
GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE,
VERIFIED_EMPTY_CONTRACT_HASH
} from '../../../../helpers'
const DUMMY_REVERT_DATA = "0xdeadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420"
const test_ovmCALL: TestDefinition = {
name: "Basic tests for ovmCALL",
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
ovmSafetyChecker: "$OVM_SAFETY_CHECKER",
messageRecord: {
nuisanceGasLeft: GAS_LIMIT
}
},
StateManager: {
owner: "$OVM_EXECUTION_MANAGER",
accounts: {
"$DUMMY_OVM_ADDRESS_1": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
"$DUMMY_OVM_ADDRESS_2": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
"$DUMMY_OVM_ADDRESS_3": {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
}
}
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmCALLER',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_1"]
},
{
functionName: 'ovmADDRESS',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_2"]
},
{
functionName: 'ovmSSTORE',
functionParams: [ NON_NULL_BYTES32, NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmSLOAD',
functionParams: [ NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSLOAD',
functionParams: [ NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmCALLER',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_1"]
},
{
functionName: 'ovmADDRESS',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_2"]
},
{
functionName: 'ovmSSTORE',
functionParams: [ NON_NULL_BYTES32, NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmSLOAD',
functionParams: [ NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_3",
[]
],
expectedReturnStatus: true,
expectedReturnValues: [true, "0x"]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
}
const test_ovmCALL_revert: TestDefinition = {
name: "Basic reverts in a code contract called via ovmCALL",
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
ovmSafetyChecker: "$OVM_SAFETY_CHECKER",
messageRecord: {
nuisanceGasLeft: 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_REVERT_HELPER"
},
"$DUMMY_OVM_ADDRESS_3": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_REVERT_HELPER"
},
"$DUMMY_OVM_ADDRESS_4": {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
}
}
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALLToRevert',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
REVERT_FLAGS.INTENTIONAL_REVERT,
DUMMY_REVERT_DATA,
GAS_LIMIT / 2,
0
]
],
expectedReturnStatus: true,
expectedReturnValues: [false, DUMMY_REVERT_DATA]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALLToRevert',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
REVERT_FLAGS.EXCEEDS_NUISANCE_GAS,
"0x",
0,
0
]
],
expectedReturnStatus: true,
expectedReturnValues: [false, "0x"]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
},
]
}
runExecutionManagerTest(test_ovmCALL)
runExecutionManagerTest(test_ovmCALL_revert)
/* Internal Imports */
import {
runExecutionManagerTest,
TestDefinition,
GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE,
ZERO_ADDRESS,
VERIFIED_EMPTY_CONTRACT_HASH,
DUMMY_BYTECODE_BYTELEN,
DUMMY_BYTECODE_HASH
} from '../../../../helpers'
const CREATED_CONTRACT_1 = "0x2bda4a99d5be88609d23b1e4ab5d1d34fb1c2feb"
const NESTED_CREATED_CONTRACT = "0xcb964b3f4162a0d4f5c997b40e19da5a546bc36f"
const test_ovmCREATE: TestDefinition = {
name: "Basic tests for ovmCREATE",
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
ovmSafetyChecker: "$OVM_SAFETY_CHECKER",
messageRecord: {
nuisanceGasLeft: 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"
},
[CREATED_CONTRACT_1]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
}
}
}
},
parameters: [
{
name: "Should correctly expose code-related opcodes for a created address once deployed",
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
DUMMY_BYTECODE,
// expect creation to succeed?
true,
[]
],
expectedReturnStatus: true,
expectedReturnValues: [CREATED_CONTRACT_1]
},
{
functionName: "ovmEXTCODESIZE",
functionParams: [CREATED_CONTRACT_1],
expectedReturnStatus: true,
expectedReturnValues: [DUMMY_BYTECODE_BYTELEN]
},
{
functionName: "ovmEXTCODEHASH",
functionParams: [CREATED_CONTRACT_1],
expectedReturnStatus: true,
expectedReturnValues: [DUMMY_BYTECODE_HASH]
},
{
functionName: "ovmEXTCODECOPY",
functionParams: [CREATED_CONTRACT_1, 0, DUMMY_BYTECODE_BYTELEN],
expectedReturnStatus: true,
expectedReturnValues: [DUMMY_BYTECODE_HASH]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "Should return 0 address correctly expose empty code-related opcodes if deployment fails",
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
DUMMY_BYTECODE,
// expect creation to succeed?
false,
[
{
functionName: 'ovmREVERT',
functionParams: ['0x1234'],
expectedReturnStatus: undefined, // TODO: use this wherever not checked
expectedReturnValues: undefined
}
]
],
expectedReturnStatus: true,
expectedReturnValues: [ZERO_ADDRESS]
},
{
functionName: "ovmEXTCODESIZE",
functionParams: [CREATED_CONTRACT_1],
expectedReturnStatus: true,
expectedReturnValues: [0]
},
{
functionName: "ovmEXTCODEHASH",
functionParams: [CREATED_CONTRACT_1],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32]
},
{
functionName: "ovmEXTCODECOPY",
functionParams: [CREATED_CONTRACT_1, 0, 256],
expectedReturnStatus: true,
expectedReturnValues: ["0x" + "00".repeat(256)]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "Basic relevant context opcodes should be accessible in initcode",
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
// code to deploy:
DUMMY_BYTECODE,
// expect creation to succeed?
true,
// steps for initcode:
[
{
functionName: 'ovmCALLER',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_1"]
},
{
functionName: 'ovmADDRESS',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: [CREATED_CONTRACT_1]
},
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: [CREATED_CONTRACT_1]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "Internal storage manipulation during initcode should be correctly persisted, and all accessible",
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
// code to deploy:
'$OVM_CALL_HELPER_CODE',
// expect creation to succeed?
true,
// steps for initcode:
[
{
functionName: 'ovmSSTORE',
functionParams: [NON_NULL_BYTES32, NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: [CREATED_CONTRACT_1]
},
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
CREATED_CONTRACT_1,
[
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
},
{
functionName: 'ovmSLOAD',
functionParams: [NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32]
}
],
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "External storage manipulation during initcode subcalls should correctly be persisted",
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
// code to deploy:
'$OVM_CALL_HELPER_CODE',
// expect creation to succeed?
true,
// steps for initcode:
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmSLOAD',
functionParams: [NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: [CREATED_CONTRACT_1]
},
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSLOAD',
functionParams: [NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
}
],
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "External storage manipulation during initcode subcalls should correctly NOT be persisted if ovmREVERTed",
preState: {
StateManager: {
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"
},
[CREATED_CONTRACT_1]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
}
},
verifiedContractStorage: {
"$DUMMY_OVM_ADDRESS_2": {
[NULL_BYTES32]: true
}
}
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
// code to deploy:
'$OVM_CALL_HELPER_CODE',
// expect ovmCREATE to successfully deploy?
false,
// steps for initcode:
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmREVERT',
functionParams: [ '0xdeadbeef' ],
expectedReturnStatus: true,
expectedReturnValues: [] // technically will return 1 single byte but impossible to assert
}
]
],
expectedReturnStatus: true,
expectedReturnValues: [ZERO_ADDRESS]
},
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSLOAD',
functionParams: [NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32]
}
],
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "Should correctly revert on invalid state access in initcode made by a call",
preState: {
StateManager: {
accounts: {
"$DUMMY_OVM_ADDRESS_1": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
[CREATED_CONTRACT_1]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
}
},
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
// code to deploy:
'$OVM_CALL_HELPER_CODE',
// expect ovmCREATE to successfully deploy?
false,
// steps for initcode:
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_3", // invalid state access, not in prestate.SM.accounts
[]
],
expectedReturnStatus: undefined,
expectedReturnValues: undefined
},
]
],
expectedReturnStatus: false,
expectedReturnValues: [REVERT_FLAGS.INVALID_STATE_ACCESS, "0x", 476756501, 0]
},
]
],
// note: this would be false in practice, but our code contracts are unsafe, so they do not enforce propagation of ISA flag.
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "Invalid state access on nested CREATE should be surfaced",
preState: {
StateManager: {
accounts: {
"$DUMMY_OVM_ADDRESS_1": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
[CREATED_CONTRACT_1]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
},
[NESTED_CREATED_CONTRACT]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
}
},
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATE',
functionParams: [
// code to deploy:
'$OVM_CALL_HELPER_CODE',
// expect ovmCREATE to successfully deploy?
false,
// steps for initcode:
[
{
functionName: 'ovmCREATE',
functionParams: [
// code to deploy:
'$OVM_CALL_HELPER_CODE',
// expect ovmCREATE to successfully deploy?
false,
// steps for initcode:
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_3", // invalid state access, not in prestate.SM.accounts
[]
],
expectedReturnStatus: undefined,
expectedReturnValues: undefined
},
]
],
expectedReturnStatus: undefined,
expectedReturnValues: undefined
},
]
],
expectedReturnStatus: false,
expectedReturnValues: [REVERT_FLAGS.INVALID_STATE_ACCESS, "0x", 476709610, 0]
},
]
],
// note: this would be false in practice, but our code contracts are unsafe, so they do not enforce propagation of ISA flag.
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
{
name: "CREATE should fail and return 0 address if out of gas",
focus: true,
preState: {
StateManager: {
accounts: {
"$DUMMY_OVM_ADDRESS_1": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
[CREATED_CONTRACT_1]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
},
[NESTED_CREATED_CONTRACT]: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20)
}
},
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCREATEToInvalid',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: [ZERO_ADDRESS]
},
]
],
// note: this would be false in practice, but our code contracts are unsafe, so they do not enforce propagation of ISA flag.
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
}
]
}
runExecutionManagerTest(test_ovmCREATE)
/* Internal Imports */
import {
runExecutionManagerTest,
TestDefinition,
GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE
} from '../../../../helpers'
const CREATED_CONTRACT_1 = "0x2bda4a99d5be88609d23b1e4ab5d1d34fb1c2feb"
const CREATED_CONTRACT_2 = "0xe0d8be8101f36ebe6b01abacec884422c39a1f62"
const test_ovmDELEGATECALL: TestDefinition = {
name: "Basic tests for ovmDELEGATECALL",
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
ovmSafetyChecker: "$OVM_SAFETY_CHECKER",
messageRecord: {
nuisanceGasLeft: GAS_LIMIT
}
},
StateManager: {
owner: "$OVM_EXECUTION_MANAGER",
accounts: {
"$DUMMY_OVM_ADDRESS_1": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
"$DUMMY_OVM_ADDRESS_2": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
"$DUMMY_OVM_ADDRESS_3": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
"$DUMMY_OVM_ADDRESS_4": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
}
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmDELEGATECALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_3",
[
{
functionName: 'ovmCALLER',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_1"]
},
{
functionName: 'ovmADDRESS',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_2"]
},
{
functionName: 'ovmSSTORE',
functionParams: [ NON_NULL_BYTES32, NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmSLOAD',
functionParams: [ NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_1"]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSLOAD',
functionParams: [ NON_NULL_BYTES32 ],
expectedReturnStatus: true,
expectedReturnValues: [NON_NULL_BYTES32]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmDELEGATECALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmCREATE',
functionParams: [
DUMMY_BYTECODE,
true,
[]
],
expectedReturnStatus: true,
expectedReturnValues: [CREATED_CONTRACT_1]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmDELEGATECALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_3",
[
{
functionName: "ovmDELEGATECALL",
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_4",
[
{
functionName: "ovmCALLER",
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: [ "$DUMMY_OVM_ADDRESS_1" ]
},
{
functionName: "ovmADDRESS",
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: [ "$DUMMY_OVM_ADDRESS_2" ]
},
{
functionName: 'ovmCREATE',
functionParams: [
DUMMY_BYTECODE,
true,
[]
],
expectedReturnStatus: true,
expectedReturnValues: [CREATED_CONTRACT_2]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: "ovmADDRESS",
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: [ "$DUMMY_OVM_ADDRESS_1" ]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
}
runExecutionManagerTest(test_ovmDELEGATECALL)
/* Internal Imports */
import {
runExecutionManagerTest,
TestDefinition,
GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE
} from '../../../../helpers'
const test_ovmREVERT: TestDefinition = {
name: "basic ovmREVERT unit tests",
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
ovmSafetyChecker: "$OVM_SAFETY_CHECKER",
messageRecord: {
nuisanceGasLeft: GAS_LIMIT
}
},
StateManager: {
owner: "$OVM_EXECUTION_MANAGER",
accounts: {
"$DUMMY_OVM_ADDRESS_1": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
}
}
},
parameters: [
{
name: "ovmREVERT inside ovmCALL should cause EM to revert",
parameters: [
{
steps: [
{
functionName: "ovmCALL",
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: "ovmREVERT",
functionParams: [ "0xdeadbeef" ],
expectedReturnStatus: false,
expectedReturnValues: [
REVERT_FLAGS.INTENTIONAL_REVERT,
"0xdeadbeef",
GAS_LIMIT / 2,
0
]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
},
// TODO: fix this. only way to do it is manually set up and call ovmREVERT directly inside a context which mirrors that during creation.
// {
// name: "ovmREVERT inside ovmCREATE ?",
// parameters: [
// {
// steps: [
// {
// functionName: "ovmCALL",
// functionParams: [
// GAS_LIMIT / 2,
// "$DUMMY_OVM_ADDRESS_1",
// [
// {
// functionName: "ovmCREATE",
// functionParams: [
// USELESS_BYTECODE,
// false, // "create will be successful?"
// [
// {
// functionName: "ovmREVERT",
// functionParams: [ "0xdeadbeef" ],
// expectedReturnStatus: false,
// expectedReturnValues: [ "0x00" ] // no return values for reversion in constructor
// },
// // TODO: check internally flagged storage here
// ]
// ],
// expectedReturnStatus: true,
// expectedReturnValues: [ CREATED_CONTRACT_1 ]
// }
// ],
// ],
// expectedReturnStatus: true,
// expectedReturnValues: []
// }
// ]
// }
// ]
// }
]
}
runExecutionManagerTest(test_ovmREVERT)
/* Internal Imports */
import {
runExecutionManagerTest,
TestDefinition,
GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE
} from '../../../../helpers'
const test_ovmSLOAD: TestDefinition = {
name: "External storage manipulation during initcode subcalls should correctly NOT be persisted if ovmREVERTed",
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
ovmSafetyChecker: "$OVM_SAFETY_CHECKER",
messageRecord: {
nuisanceGasLeft: 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"
},
},
verifiedContractStorage: {
"$DUMMY_OVM_ADDRESS_1": {
[NON_NULL_BYTES32]: true
}
}
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
}
runExecutionManagerTest(test_ovmSLOAD)
/* Internal Imports */
import {
runExecutionManagerTest,
TestDefinition,
GAS_LIMIT,
NULL_BYTES32,
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE
} from '../../../../helpers'
const test_ovmSTATICCALL: TestDefinition = {
name: "Basic checks on staticcall",
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
ovmSafetyChecker: "$OVM_SAFETY_CHECKER",
messageRecord: {
nuisanceGasLeft: 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"
},
},
verifiedContractStorage: {
"$DUMMY_OVM_ADDRESS_2": {
[NON_NULL_BYTES32]: true
}
}
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NULL_BYTES32],
expectedReturnStatus: false,
expectedReturnValues: [REVERT_FLAGS.STATIC_VIOLATION, "0x", GAS_LIMIT / 2, 0]
},
{
functionName: 'ovmCREATE',
functionParams: [
DUMMY_BYTECODE,
false,
[]
],
expectedReturnStatus: false,
expectedReturnValues: [REVERT_FLAGS.STATIC_VIOLATION, "0x", GAS_LIMIT / 2, 0]
},
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32]
},
{
functionName: 'ovmCALLER',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_1"]
},
{
functionName: 'ovmADDRESS',
functionParams: [],
expectedReturnStatus: true,
expectedReturnValues: ["$DUMMY_OVM_ADDRESS_2"]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSLOAD',
functionParams: [NON_NULL_BYTES32],
expectedReturnStatus: true,
expectedReturnValues: [NULL_BYTES32]
},
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NULL_BYTES32],
expectedReturnStatus: false,
expectedReturnValues: [REVERT_FLAGS.STATIC_VIOLATION, "0x", 867484476, 2906]
},
{
functionName: 'ovmCREATE',
functionParams: [
DUMMY_BYTECODE,
false,
[]
],
expectedReturnStatus: false,
expectedReturnValues: [REVERT_FLAGS.STATIC_VIOLATION, "0x", 867484476, 2906]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_2",
[]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
{
functionName: 'ovmSSTORE',
functionParams: [NULL_BYTES32, NULL_BYTES32],
expectedReturnStatus: false,
expectedReturnValues: [REVERT_FLAGS.STATIC_VIOLATION, "0x", GAS_LIMIT / 2, 33806]
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmSTATICCALLToRevert',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
REVERT_FLAGS.STATIC_VIOLATION,
"0x",
GAS_LIMIT / 2,
0
]
],
expectedReturnStatus: true,
expectedReturnValues: [false, "0x"]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
},
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmSTATICCALL',
functionParams: [
GAS_LIMIT,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmSTATICCALLToRevert',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
REVERT_FLAGS.STATIC_VIOLATION,
"0x",
GAS_LIMIT / 2,
0
]
],
expectedReturnStatus: true,
expectedReturnValues: [false, "0x"]
},
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
],
expectedReturnStatus: true,
expectedReturnValues: []
}
]
}
]
}
runExecutionManagerTest(test_ovmSTATICCALL)
import { ethers } from '@nomiclabs/buidler' import { ethers } from '@nomiclabs/buidler'
import { hexZeroPad } from 'ethers/lib/utils'
export const encodeRevertData = ( export const encodeRevertData = (
flag: number, flag: number,
...@@ -6,10 +7,25 @@ export const encodeRevertData = ( ...@@ -6,10 +7,25 @@ export const encodeRevertData = (
nuisanceGasLeft: number = 0, nuisanceGasLeft: number = 0,
ovmGasRefund: number = 0 ovmGasRefund: number = 0
): string => { ): string => {
return ethers.utils.defaultAbiCoder.encode( const abiEncoded: string = ethers.utils.defaultAbiCoder.encode(
['uint256','uint256','uint256','bytes'], ['uint256','uint256','uint256','bytes'],
[flag, nuisanceGasLeft, ovmGasRefund, data] [flag, nuisanceGasLeft, ovmGasRefund, data]
) )
return abiEncoded
}
export const decodeRevertData = (
revertData: string
): any => {
// const length: number = revertData.length/2 - 1
// const reencodedRevertData = '0x' + hexZeroPad('0x' + length.toString(16), 32) + revertData.slice(2)
const decoded = ethers.utils.defaultAbiCoder.decode(
['uint256','uint256','uint256','bytes'],
revertData
)
console.log(`flag is: ${decoded[0].toNumber()}`)
return '[revertFlag:' + Object.keys(REVERT_FLAGS)[decoded[0]] + ', nuisanceGasLeft:' + decoded[1] + ', ovmGasRefund: ' + decoded[2] + ', data: ' + decoded[3] + ']'
} }
export const REVERT_FLAGS = { export const REVERT_FLAGS = {
...@@ -18,5 +34,8 @@ export const REVERT_FLAGS = { ...@@ -18,5 +34,8 @@ export const REVERT_FLAGS = {
INTENTIONAL_REVERT: 2, INTENTIONAL_REVERT: 2,
EXCEEDS_NUISANCE_GAS: 3, EXCEEDS_NUISANCE_GAS: 3,
INVALID_STATE_ACCESS: 4, INVALID_STATE_ACCESS: 4,
UNSAFE_BYTECODE: 5 UNSAFE_BYTECODE: 5,
CREATE_COLLISION: 6,
STATIC_VIOLATION: 7,
CREATE_EXCEPTION: 8
} }
...@@ -20,4 +20,4 @@ export const NON_NULL_BYTES32 = makeHexString('11', 32) ...@@ -20,4 +20,4 @@ export const NON_NULL_BYTES32 = makeHexString('11', 32)
export const ZERO_ADDRESS = makeAddress('00') export const ZERO_ADDRESS = makeAddress('00')
export const NON_ZERO_ADDRESS = makeAddress('11') export const NON_ZERO_ADDRESS = makeAddress('11')
export const FORCE_INCLUSION_PERIOD_SECONDS = 600 export const VERIFIED_EMPTY_CONTRACT_HASH = makeHexString('69', 32)
/* External Imports */
import { keccak256 } from 'ethers/lib/utils'
export const DUMMY_BYTECODE = '0x123412341234'
export const DUMMY_BYTECODE_BYTELEN = 6
export const DUMMY_BYTECODE_HASH = keccak256(DUMMY_BYTECODE)
export * from './accounts' export * from './accounts'
export * from './bytes32' export * from './bytes32'
export * from './context' export * from './context'
export * from './bytecode'
...@@ -6,4 +6,5 @@ export * from './mocks' ...@@ -6,4 +6,5 @@ export * from './mocks'
export * from './buffer-utils' export * from './buffer-utils'
export * from './byte-utils' export * from './byte-utils'
export * from './codec' export * from './codec'
export * from './data' export * from './data'
\ No newline at end of file export * from './test-utils'
export * from './test-generation'
export * from './test-parsing'
export * from './test.types'
/* External Imports */ /* External Imports */
import { Contract } from 'ethers' import { Contract } from 'ethers'
import { Interface, AbiCoder } from 'ethers/lib/utils'
import * as path from 'path'
import { REVERT_FLAGS, encodeRevertData, decodeRevertData } from '../codec'
/* Internal Imports */ /* Internal Imports */
import { TestStep } from './test.types' import { TestStep } from './test.types'
const abi = new AbiCoder()
const getContractDefinition = (name: string): any => {
return require(path.join(__dirname, '../../../artifacts', `${name}.json`))
}
export const getInitcode = (name: string): string => {
return getContractDefinition(name).bytecode
}
export const getBytecode = (name: string): string => {
return getContractDefinition(name).deployedBytecode
}
export interface TestCallGenerator { export interface TestCallGenerator {
getCalldata(): string getCalldata(): string
shouldCallSucceed(): boolean
getReturnData(): string getReturnData(): string
getFunctionName(): string
interpretActualReturnData(data: string, succeeded: boolean): string
} }
export class DefaultTestGenerator implements TestCallGenerator { export class DefaultTestGenerator implements TestCallGenerator {
constructor( constructor(
protected ovmExecutionManager: Contract, protected ovmExecutionManager: Contract,
protected ovmCallHelper: Contract, protected ovmCallHelper: Contract,
protected step: TestStep protected ovmCreateStorer: Contract,
protected ovmCreateHelper: Interface,
protected ovmRevertHelper: Contract,
protected ovmInvalidHelper: Contract,
protected step: TestStep,
) {} ) {}
getFunctionParams(): any[] { getFunctionParams(): any[] {
...@@ -21,7 +45,7 @@ export class DefaultTestGenerator implements TestCallGenerator { ...@@ -21,7 +45,7 @@ export class DefaultTestGenerator implements TestCallGenerator {
} }
getReturnValues(): any[] { getReturnValues(): any[] {
return this.step.returnValues return this.step.expectedReturnValues
} }
getCalldata(): string { getCalldata(): string {
...@@ -31,11 +55,42 @@ export class DefaultTestGenerator implements TestCallGenerator { ...@@ -31,11 +55,42 @@ export class DefaultTestGenerator implements TestCallGenerator {
) )
} }
shouldCallSucceed(): boolean {
return this.step.expectedReturnStatus
}
getFunctionName(): string {
return this.step.functionName
}
getReturnData(): string { getReturnData(): string {
return this.ovmExecutionManager.interface.encodeFunctionResult( let expectedReturnData
this.step.functionName, if (this.step.expectedReturnStatus) {
this.getReturnValues() expectedReturnData = this.ovmExecutionManager.interface.encodeFunctionResult(
this.step.functionName,
this.getReturnValues()
) )
} else {
expectedReturnData = encodeRevertData(
this.step.expectedReturnValues[0],
this.step.expectedReturnValues[1],
this.step.expectedReturnValues[2],
this.step.expectedReturnValues[3]
)
}
return expectedReturnData
}
interpretActualReturnData(data: string, succeeded: boolean): string {
let interpretation: string = 'call to EM.' + this.step.functionName + ' returned values:'
interpretation += succeeded ?
this.ovmExecutionManager.interface.decodeFunctionResult(
this.step.functionName,
data
)
:
decodeRevertData(data)
return interpretation
} }
} }
...@@ -46,6 +101,10 @@ export class ovmCALLGenerator extends DefaultTestGenerator { ...@@ -46,6 +101,10 @@ export class ovmCALLGenerator extends DefaultTestGenerator {
step, step,
this.ovmExecutionManager, this.ovmExecutionManager,
this.ovmCallHelper, this.ovmCallHelper,
this.ovmCreateStorer,
this.ovmCreateHelper,
this.ovmRevertHelper,
this.ovmInvalidHelper
) )
}) })
} }
...@@ -57,51 +116,454 @@ export class ovmCALLGenerator extends DefaultTestGenerator { ...@@ -57,51 +116,454 @@ export class ovmCALLGenerator extends DefaultTestGenerator {
this.ovmCallHelper.interface.encodeFunctionData( this.ovmCallHelper.interface.encodeFunctionData(
'runSteps', 'runSteps',
[ [
{ this.getCalleeGenerators().map((calleeGenerator) => {
callsToEM: this.getCalleeGenerators().map((calleeGenerator) => { return calleeGenerator.getCalldata()
return calleeGenerator.getCalldata() }),
}), !this.step.expectedReturnStatus,
shouldRevert: !this.step.returnStatus this.ovmCreateStorer.address
}
] ]
) )
] ]
} }
getReturnValues(): any[] { getReturnValues(): any[] {
return [ if (this.step.expectedReturnValues.length <= 1) {
this.step.returnStatus, return [
this.ovmCallHelper.interface.encodeFunctionResult( !this.step.expectedReturnValues[0],
this.ovmCallHelper.interface.encodeFunctionResult(
'runSteps',
[
this.getCalleeGenerators().map((calleeGenerator) => {
return {
success: calleeGenerator.shouldCallSucceed(),
data: calleeGenerator.getReturnData()
}
})
]
)
]
} else {
return this.step.expectedReturnValues
}
}
interpretActualReturnData(data: string, success: boolean): string {
if (!success) {
return 'ovmCALL-type reverted with flag:' + decodeRevertData(data)
}
if (this.step.expectedReturnValues.length >1) {
return 'ovmCALL-type returned successfully with overridden return data: ' + data
}
const resultOfOvmCALL = this.ovmExecutionManager.interface.decodeFunctionResult(this.step.functionName, data)
const resultOfSubcalls = this.ovmCallHelper.interface.decodeFunctionResult(
'runSteps', 'runSteps',
[ resultOfOvmCALL[1]
this.getCalleeGenerators().map((calleeGenerator) => { )[0]
return {
success: true, const calleeGenerators = this.getCalleeGenerators()
data: calleeGenerator.getReturnData() const interpretedResults = resultOfSubcalls.map((result, i) => {
} const generator = calleeGenerators[i]
}) const EMsuccess = result[0]
] const EMdata = result[1]
) return 'subcall ' + i + '(' + generator.getFunctionName() + ') had return status: ' + EMsuccess + ' and appears to have done: ' + generator.interpretActualReturnData(EMdata, EMsuccess)
})
return 'ovmCALL returned ' + resultOfOvmCALL[0] + ' \n with subcalls:' + JSON.stringify(interpretedResults) + '\n'
}
}
export class ovmCREATEGenerator extends DefaultTestGenerator {
getInitcodeGenerators(): TestCallGenerator[] {
return (this.step.functionParams[2] as TestStep[]).map((step) => {
return getTestGenerator(
step,
this.ovmExecutionManager,
this.ovmCallHelper,
this.ovmCreateStorer,
this.ovmCreateHelper,
this.ovmRevertHelper,
this.ovmInvalidHelper
)
})
}
getFunctionParams(): any[] {
return [
getInitcode('Helper_CodeContractForCreates') +
this.ovmCreateHelper.encodeDeploy([
this.getInitcodeGenerators().map((initcodeGenerator) => {
return initcodeGenerator.getCalldata()
}),
!this.step.expectedReturnStatus,
this.step.functionParams[0],
this.ovmCreateStorer.address
]).slice(2)
] ]
} }
getReturnData(): string {
const expectedDirectEMReturnData = this.step.expectedReturnStatus ?
this.ovmExecutionManager.interface.encodeFunctionResult(
this.step.functionName,
this.getReturnValues()
)
:
encodeRevertData(
this.step.expectedReturnValues[0],
this.step.expectedReturnValues[1],
this.step.expectedReturnValues[2],
this.step.expectedReturnValues[3]
)
const responsesShouldBeReverted = !this.step.functionParams[1]
const expectedStoredValues = responsesShouldBeReverted ? [] : this.getInitcodeGenerators().map((initcodeGenerator) => {
return {
success: initcodeGenerator.shouldCallSucceed(), // TODO: figure out if we need this and expose in generator interface if so.
data: initcodeGenerator.getReturnData()
}
})
const expectedReturnData = abi.encode(
['bytes', 'bytes'],
[
expectedDirectEMReturnData,
this.ovmCreateStorer.interface.encodeFunctionResult(
'getLastResponses',
[
expectedStoredValues
]
)
]
)
return expectedReturnData
}
interpretActualReturnData(data: string, success: boolean): string {
const ovmCREATEDataAndInitcodeResults = abi.decode(
['bytes', 'bytes'],
data
)
const ovmCREATEData = ovmCREATEDataAndInitcodeResults[0]
if (!success) {
return 'ovmCREATE reverted with: ' + decodeRevertData(ovmCREATEData.toString())
}
// const decodedDataFromOvmCREATE = this.ovmExecutionManager.interface.decodeFunctionResult(this.step.functionName, ovmCREATEData)
const initcodeResultsRaw = ovmCREATEDataAndInitcodeResults[1]
const initcodeResults = this.ovmCreateStorer.interface.decodeFunctionResult(
'getLastResponses',
initcodeResultsRaw
)[0]
const interpretedInitcodeResults = initcodeResults.map((result, i) => {
return '\n initcode subcall ' + i + ' had response status ' + result[0] + ' and appears to have done: ' + this.getInitcodeGenerators()[i].interpretActualReturnData(result[1], result[0])
})
return JSON.stringify(interpretedInitcodeResults)
}
} }
class ovmCALLToRevertGenerator extends DefaultTestGenerator {
getCalldata(): string {
return this.ovmExecutionManager.interface.encodeFunctionData(
'ovmCALL',
[
this.step.functionParams[0],
this.step.functionParams[1],
this.ovmRevertHelper.interface.encodeFunctionData(
'doRevert',
[
encodeRevertData(
this.step.functionParams[2][0],
this.step.functionParams[2][1],
this.step.functionParams[2][2],
this.step.functionParams[2][3]
)
]
)
]
)
}
getReturnData(): string {
let expectedReturnData
if (this.step.expectedReturnStatus) {
expectedReturnData = this.ovmExecutionManager.interface.encodeFunctionResult(
'ovmCALL',
this.step.expectedReturnValues
)
} else {
expectedReturnData = encodeRevertData(
this.step.expectedReturnValues[0],
this.step.expectedReturnValues[1],
this.step.expectedReturnValues[2],
this.step.expectedReturnValues[3]
)
}
return expectedReturnData
}
interpretActualReturnData(data: string, success: boolean): string {
if (success) {
return 'ovmCALL to revert heler, which succeeded with return params:' + JSON.stringify(
this.ovmExecutionManager.interface.decodeFunctionResult(
'ovmCALL',
data
)
)
} else {
return 'ovmCALL to revert helper did itself revert with flag:' + JSON.stringify(
decodeRevertData(data)
)
}
}
}
class ovmSTATICCALLToRevertGenerator extends DefaultTestGenerator {
getCalldata(): string {
return this.ovmExecutionManager.interface.encodeFunctionData(
'ovmSTATICCALL',
[
this.step.functionParams[0],
this.step.functionParams[1],
this.ovmRevertHelper.interface.encodeFunctionData(
'doRevert',
[
encodeRevertData(
this.step.functionParams[2][0],
this.step.functionParams[2][1],
this.step.functionParams[2][2],
this.step.functionParams[2][3]
)
]
)
]
)
}
getReturnData(): string {
let expectedReturnData
if (this.step.expectedReturnStatus) {
expectedReturnData = this.ovmExecutionManager.interface.encodeFunctionResult(
'ovmCALL',
this.step.expectedReturnValues
)
} else {
expectedReturnData = encodeRevertData(
this.step.expectedReturnValues[0],
this.step.expectedReturnValues[1],
this.step.expectedReturnValues[2],
this.step.expectedReturnValues[3]
)
}
return expectedReturnData
}
interpretActualReturnData(data: string, success: boolean): string {
if (success) {
return 'ovmCALL to revert heler, which succeeded with return params:' + JSON.stringify(
this.ovmExecutionManager.interface.decodeFunctionResult(
'ovmCALL',
data
)
)
} else {
return 'ovmCALL to revert helper did itself revert with flag:' + JSON.stringify(
decodeRevertData(data)
)
}
}
}
class ovmCALLToInvalidGenerator extends DefaultTestGenerator {
getCalldata(): string {
return this.ovmExecutionManager.interface.encodeFunctionData(
'ovmCALL',
[
this.step.functionParams[0],
this.step.functionParams[1],
this.ovmInvalidHelper.interface.encodeFunctionData(
'doInvalid',
[]
)
]
)
}
getReturnData(): string {
let expectedReturnData
if (this.step.expectedReturnStatus) {
expectedReturnData = this.ovmExecutionManager.interface.encodeFunctionResult(
'ovmCALL',
this.step.expectedReturnValues
)
} else {
expectedReturnData = encodeRevertData(
this.step.expectedReturnValues[2][0],
this.step.expectedReturnValues[2][1],
this.step.expectedReturnValues[2][2],
this.step.expectedReturnValues[2][3]
)
}
return expectedReturnData
}
interpretActualReturnData(data: string, success: boolean): string {
if (success) {
return 'ovmCALL to InvalidJump/OutOfGas heler, which succeeded with return params:' + JSON.stringify(
this.ovmExecutionManager.interface.decodeFunctionResult(
'ovmCALL',
data
)
)
} else {
return 'ovmCALL to InvalidJump/OutOfGas helper did itself revert with flag:' + JSON.stringify(
decodeRevertData(data)
)
}
}
}
class ovmCREATEToInvalidGenerator extends DefaultTestGenerator {
getCalldata(): string {
return this.ovmExecutionManager.interface.encodeFunctionData(
'ovmCREATE',
[
getInitcode('Helper_CodeContractForInvalidInCreation')
]
)
}
getReturnData(): string {
let expectedEMResponse
if (this.step.expectedReturnStatus) {
expectedEMResponse = this.ovmExecutionManager.interface.encodeFunctionResult(
'ovmCREATE',
this.step.expectedReturnValues
)
} else {
expectedEMResponse = encodeRevertData(
this.step.expectedReturnValues[2][0],
this.step.expectedReturnValues[2][1],
this.step.expectedReturnValues[2][2],
this.step.expectedReturnValues[2][3]
)
}
return abi.encode(
['bytes', 'bytes'],
[
expectedEMResponse,
this.ovmCreateStorer.interface.encodeFunctionResult('getLastResponses', [[]])
]
)
}
interpretActualReturnData(data: string, success: boolean): string {
const EMResponse = abi.decode(
['bytes', 'bytes'],
data
)[0]
console.log(`EMResponse is ${EMResponse}, success is ${success}`)
if (success) {
return 'ovmCREATE to InvalidJump/OutOfGas IN CONSTRUCTOR heler, which succeeded returning address:' + JSON.stringify(
this.ovmExecutionManager.interface.decodeFunctionResult(
'ovmCREATE',
EMResponse
)
)
} else {
return 'ovmCALL to InvalidJump/OutOfGas IN CONSTRUCTOR heler did itself revert with flag:' + JSON.stringify(
decodeRevertData(EMResponse)
)
}
}
}
export const getTestGenerator = ( export const getTestGenerator = (
step: TestStep, step: TestStep,
ovmExecutionManager: Contract, ovmExecutionManager: Contract,
ovmCallHelper: Contract ovmCallHelper: Contract,
ovmCreateStorer: Contract,
ovmCreateHelper: Interface,
ovmRevertHelper: Contract,
ovmInvalidHelper: Contract
): TestCallGenerator => { ): TestCallGenerator => {
switch (step.functionName) { switch (step.functionName) {
case 'ovmCALL': case 'ovmCALL':
case 'ovmDELEGATECALL':
case 'ovmSTATICCALL':
return new ovmCALLGenerator( return new ovmCALLGenerator(
ovmExecutionManager, ovmExecutionManager,
ovmCallHelper, ovmCallHelper,
ovmCreateStorer,
ovmCreateHelper,
ovmRevertHelper,
ovmInvalidHelper,
step
)
case 'ovmCREATE':
return new ovmCREATEGenerator(
ovmExecutionManager,
ovmCallHelper,
ovmCreateStorer,
ovmCreateHelper,
ovmRevertHelper,
ovmInvalidHelper,
step
)
case 'ovmCALLToRevert':
return new ovmCALLToRevertGenerator(
ovmExecutionManager,
ovmCallHelper,
ovmCreateStorer,
ovmCreateHelper,
ovmRevertHelper,
ovmInvalidHelper,
step
)
case 'ovmCALLToInvalid':
return new ovmCALLToInvalidGenerator(
ovmExecutionManager,
ovmCallHelper,
ovmCreateStorer,
ovmCreateHelper,
ovmRevertHelper,
ovmInvalidHelper,
step
)
case 'ovmSTATICCALLToRevert':
return new ovmSTATICCALLToRevertGenerator(
ovmExecutionManager,
ovmCallHelper,
ovmCreateStorer,
ovmCreateHelper,
ovmRevertHelper,
ovmInvalidHelper,
step step
) )
case 'ovmCREATEToInvalid':
return new ovmCREATEToInvalidGenerator(
ovmExecutionManager,
ovmCallHelper,
ovmCreateStorer,
ovmCreateHelper,
ovmRevertHelper,
ovmInvalidHelper,
step
)
default: default:
return new DefaultTestGenerator( return new DefaultTestGenerator(
ovmExecutionManager, ovmExecutionManager,
ovmCallHelper, ovmCallHelper,
ovmCreateStorer,
ovmCreateHelper,
ovmRevertHelper,
ovmInvalidHelper,
step step
) )
} }
......
...@@ -3,18 +3,26 @@ import { expect } from '../../setup' ...@@ -3,18 +3,26 @@ import { expect } from '../../setup'
/* External Imports */ /* External Imports */
import { Contract } from 'ethers' import { Contract } from 'ethers'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { ethers } from '@nomiclabs/buidler'
/* Internal Imports */ /* Internal Imports */
import { getModifiableStorageFactory } from '../storage/contract-storage' import { getModifiableStorageFactory } from '../storage/contract-storage'
import { GAS_LIMIT } from '../constants' import { GAS_LIMIT, NON_NULL_BYTES32 } from '../constants'
import { getTestGenerator } from './test-generation' import { getTestGenerator, getInitcode, getBytecode } from './test-generation'
import { TestParameters, TestDefinition, isTestDefinition } from './test.types' import { TestParameters, TestDefinition, isTestDefinition } from './test.types'
import { int } from '@nomiclabs/buidler/internal/core/params/argumentTypes'
const getDummyOVMAddress = (kv: string): string => {
return '0x' + (kv.split('$DUMMY_OVM_ADDRESS_')[1] + '0').repeat(20)
}
const setPlaceholderStrings = ( const setPlaceholderStrings = (
test: any, test: any,
ovmExecutionManager: Contract, ovmExecutionManager: Contract,
ovmStateManager: Contract, ovmStateManager: Contract,
ovmCallHelper: Contract ovmSafetyChecker: Contract,
ovmCallHelper: Contract,
ovmRevertHelper: Contract,
): any => { ): any => {
const setPlaceholder = ( const setPlaceholder = (
kv: string kv: string
...@@ -23,10 +31,16 @@ const setPlaceholderStrings = ( ...@@ -23,10 +31,16 @@ const setPlaceholderStrings = (
return ovmExecutionManager.address return ovmExecutionManager.address
} else if (kv === '$OVM_STATE_MANAGER') { } else if (kv === '$OVM_STATE_MANAGER') {
return ovmStateManager.address return ovmStateManager.address
} else if (kv === '$OVM_SAFETY_CHECKER') {
return ovmSafetyChecker.address
} else if (kv === '$OVM_CALL_HELPER_CODE') {
return getBytecode('Helper_CodeContractForCalls')
} else if (kv === '$OVM_CALL_HELPER') { } else if (kv === '$OVM_CALL_HELPER') {
return ovmCallHelper.address return ovmCallHelper.address
} else if (kv === '$OVM_REVERT_HELPER') {
return ovmRevertHelper.address
} else if (kv.startsWith('$DUMMY_OVM_ADDRESS_')) { } else if (kv.startsWith('$DUMMY_OVM_ADDRESS_')) {
return '0x' + kv.split('$DUMMY_OVM_ADDRESS_')[1].padStart(40, '0') return getDummyOVMAddress(kv)
} else { } else {
return kv return kv
} }
...@@ -38,7 +52,9 @@ const setPlaceholderStrings = ( ...@@ -38,7 +52,9 @@ const setPlaceholderStrings = (
element, element,
ovmExecutionManager, ovmExecutionManager,
ovmStateManager, ovmStateManager,
ovmCallHelper ovmSafetyChecker,
ovmCallHelper,
ovmRevertHelper
) )
}) })
} else if (typeof test === 'object' && test !== null) { } else if (typeof test === 'object' && test !== null) {
...@@ -54,7 +70,9 @@ const setPlaceholderStrings = ( ...@@ -54,7 +70,9 @@ const setPlaceholderStrings = (
test[replacedKey], test[replacedKey],
ovmExecutionManager, ovmExecutionManager,
ovmStateManager, ovmStateManager,
ovmCallHelper ovmSafetyChecker,
ovmCallHelper,
ovmRevertHelper
) )
} }
} else if (typeof test === 'string') { } else if (typeof test === 'string') {
...@@ -68,9 +86,12 @@ const fixtureDeployContracts = async (): Promise <{ ...@@ -68,9 +86,12 @@ const fixtureDeployContracts = async (): Promise <{
OVM_SafetyChecker: Contract, OVM_SafetyChecker: Contract,
OVM_StateManager: Contract, OVM_StateManager: Contract,
OVM_ExecutionManager: Contract, OVM_ExecutionManager: Contract,
OVM_CallHelper: Contract OVM_CallHelper: Contract,
OVM_RevertHelper: Contract,
OVM_CreateStorer: Contract,
OVM_InvalidHelper: Contract
}> => { }> => {
const Factory__OVM_SafetyChecker = await getModifiableStorageFactory( const Factory__OVM_SafetyChecker = await ethers.getContractFactory(
'OVM_SafetyChecker' 'OVM_SafetyChecker'
) )
const Factory__OVM_StateManager = await getModifiableStorageFactory( const Factory__OVM_StateManager = await getModifiableStorageFactory(
...@@ -82,17 +103,33 @@ const fixtureDeployContracts = async (): Promise <{ ...@@ -82,17 +103,33 @@ const fixtureDeployContracts = async (): Promise <{
const Factory__Helper_CodeContractForCalls = await getModifiableStorageFactory( const Factory__Helper_CodeContractForCalls = await getModifiableStorageFactory(
'Helper_CodeContractForCalls' 'Helper_CodeContractForCalls'
) )
const Factory__Helper_CodeContractForReverts = await ethers.getContractFactory(
'Helper_CodeContractForReverts'
)
const Factory__Helper_CreateEMResponsesStorer = await ethers.getContractFactory(
'Helper_CreateEMResponsesStorer'
)
const Helper_CodeContractForInvalid = await ethers.getContractFactory(
'Helper_CodeContractForInvalid'
)
const OVM_SafetyChecker = await Factory__OVM_SafetyChecker.deploy() const OVM_SafetyChecker = await Factory__OVM_SafetyChecker.deploy()
const OVM_StateManager = await Factory__OVM_StateManager.deploy()
const OVM_ExecutionManager = await Factory__OVM_ExecutionManager.deploy(OVM_SafetyChecker.address) const OVM_ExecutionManager = await Factory__OVM_ExecutionManager.deploy(OVM_SafetyChecker.address)
const OVM_StateManager = await Factory__OVM_StateManager.deploy(OVM_ExecutionManager.address)
const OVM_CallHelper = await Factory__Helper_CodeContractForCalls.deploy() const OVM_CallHelper = await Factory__Helper_CodeContractForCalls.deploy()
const OVM_RevertHelper = await Factory__Helper_CodeContractForReverts.deploy()
const OVM_CreateStorer = await Factory__Helper_CreateEMResponsesStorer.deploy()
const OVM_InvalidHelper = await Helper_CodeContractForInvalid.deploy()
return { return {
OVM_SafetyChecker, OVM_SafetyChecker,
OVM_StateManager, OVM_StateManager,
OVM_ExecutionManager, OVM_ExecutionManager,
OVM_CallHelper, OVM_CallHelper,
OVM_RevertHelper,
OVM_CreateStorer,
OVM_InvalidHelper
} }
} }
...@@ -119,14 +156,22 @@ export const runExecutionManagerTest = ( ...@@ -119,14 +156,22 @@ export const runExecutionManagerTest = (
} }
) )
} else { } else {
let OVM_SafetyChecker: Contract
let OVM_StateManager: Contract let OVM_StateManager: Contract
let OVM_ExecutionManager: Contract let OVM_ExecutionManager: Contract
let OVM_CallHelper: Contract let OVM_CallHelper: Contract
let OVM_RevertHelper: Contract
let OVM_CreateStorer: Contract
let OVM_InvalidHelper: Contract
beforeEach(async () => { beforeEach(async () => {
const contracts = await fixtureDeployContracts() const contracts = await fixtureDeployContracts()
OVM_SafetyChecker = contracts.OVM_SafetyChecker
OVM_StateManager = contracts.OVM_StateManager OVM_StateManager = contracts.OVM_StateManager
OVM_ExecutionManager = contracts.OVM_ExecutionManager OVM_ExecutionManager = contracts.OVM_ExecutionManager
OVM_CallHelper = contracts.OVM_CallHelper OVM_CallHelper = contracts.OVM_CallHelper
OVM_RevertHelper = contracts.OVM_RevertHelper
OVM_CreateStorer = contracts.OVM_CreateStorer
OVM_InvalidHelper = contracts.OVM_InvalidHelper
}) })
let replacedParams: TestParameters let replacedParams: TestParameters
...@@ -136,13 +181,17 @@ export const runExecutionManagerTest = ( ...@@ -136,13 +181,17 @@ export const runExecutionManagerTest = (
cloneDeep(parameters), cloneDeep(parameters),
OVM_ExecutionManager, OVM_ExecutionManager,
OVM_StateManager, OVM_StateManager,
OVM_CallHelper OVM_SafetyChecker,
OVM_CallHelper,
OVM_RevertHelper
) )
replacedTest = setPlaceholderStrings( replacedTest = setPlaceholderStrings(
cloneDeep(test), cloneDeep(test),
OVM_ExecutionManager, OVM_ExecutionManager,
OVM_StateManager, OVM_StateManager,
OVM_CallHelper OVM_SafetyChecker,
OVM_CallHelper,
OVM_RevertHelper
) )
}) })
...@@ -153,21 +202,24 @@ export const runExecutionManagerTest = ( ...@@ -153,21 +202,24 @@ export const runExecutionManagerTest = (
afterEach(async () => { afterEach(async () => {
await OVM_ExecutionManager.__checkContractStorage({ await OVM_ExecutionManager.__checkContractStorage({
...replacedTest.preState.ExecutionManager,
...replacedTest.postState.ExecutionManager ...replacedTest.postState.ExecutionManager
}) })
await OVM_StateManager.__checkContractStorage({ await OVM_StateManager.__checkContractStorage({
...replacedTest.preState.StateManager,
...replacedTest.postState.StateManager ...replacedTest.postState.StateManager
}) })
}) })
parameters.steps.map((step, idx) => { parameters.steps.map((step, idx) => {
it(`should run test: ${test.name} ${idx}`, async () => { const scopedFunction = (!!test.focus) ? it.only : it
scopedFunction(`should run test: ${test.name} ${idx}`, async () => {
const testGenerator = getTestGenerator( const testGenerator = getTestGenerator(
replacedParams.steps[idx], replacedParams.steps[idx],
OVM_ExecutionManager, OVM_ExecutionManager,
OVM_CallHelper OVM_CallHelper,
OVM_CreateStorer,
(await ethers.getContractFactory('Helper_CodeContractForCreates')).interface,
OVM_RevertHelper,
OVM_InvalidHelper
) )
const callResult = await OVM_ExecutionManager.provider.call({ const callResult = await OVM_ExecutionManager.provider.call({
...@@ -182,7 +234,14 @@ export const runExecutionManagerTest = ( ...@@ -182,7 +234,14 @@ export const runExecutionManagerTest = (
gasLimit: GAS_LIMIT gasLimit: GAS_LIMIT
}) })
expect(callResult).to.equal(testGenerator.getReturnData()) const interpretation = testGenerator.interpretActualReturnData(callResult, true)
console.log('interpretation of actual results:\n' + interpretation) // in future we can add conditional here but for now always assume succeed
const interpretationOfExpected = testGenerator.interpretActualReturnData(testGenerator.getReturnData(), true)
console.log('interpretation of expected: \n' + interpretationOfExpected)
expect(callResult).to.equal(testGenerator.getReturnData()) //, 'got bad response, looks like it did:\n' + testGenerator.interpretActualReturnData(callResult))
}) })
}) })
} }
......
...@@ -5,9 +5,9 @@ export type SolidityFunctionParameter = string | number | BigNumber ...@@ -5,9 +5,9 @@ export type SolidityFunctionParameter = string | number | BigNumber
export interface TestStep { export interface TestStep {
functionName: string functionName: string
functionParams: Array<SolidityFunctionParameter | TestStep[]> functionParams: Array<SolidityFunctionParameter | SolidityFunctionParameter[] | TestStep[] | boolean>
returnStatus: boolean expectedReturnStatus: boolean
returnValues: any[] expectedReturnValues: any[]
} }
export interface TestParameters { export interface TestParameters {
...@@ -16,6 +16,7 @@ export interface TestParameters { ...@@ -16,6 +16,7 @@ export interface TestParameters {
export interface TestDefinition { export interface TestDefinition {
name: string name: string
focus?: boolean
preState?: { preState?: {
ExecutionManager?: any, ExecutionManager?: any,
StateManager?: any StateManager?: any
......
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