Commit b284dc39 authored by smartcontracts's avatar smartcontracts Committed by Kelvin Fichter

refactor[contracts]: Turn ExecutionManagerWrapper into a predeployed contract (#808)

* wip: Started working on L2 contract testing revamp

* test: clean tests for ProxyEOA

* style: clean imports for ProxyEOA tests

* test: port tests for ECDSAContractAccount

* fix tests and add wrapper to dump

* fix: add em wrapper to l2 deploy

* ffix: add comments to wrapper contract

* fix: add more comments

* fix: add smock comment for unbind

* Update packages/smock/src/smockit/binding.ts
parent 245136f1
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// @unsupported: evm
pragma solidity >0.5.0 <0.8.0; pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
...@@ -74,7 +73,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -74,7 +73,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// 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( require(
transaction.sender() == address(this), transaction.sender() == Lib_ExecutionManagerWrapper.ovmADDRESS(),
"Signature provided for EOA transaction execution is invalid." "Signature provided for EOA transaction execution is invalid."
); );
...@@ -138,6 +137,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -138,6 +137,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
), ),
"Value could not be transferred to recipient." "Value could not be transferred to recipient."
); );
return (true, bytes("")); return (true, bytes(""));
} else { } else {
return transaction.to.call(transaction.data); return transaction.to.call(transaction.data);
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// @unsupported: evm
pragma solidity >0.5.0 <0.8.0; pragma solidity >0.5.0 <0.8.0;
/* Library Imports */ /* Library Imports */
import { Lib_Bytes32Utils } from "../../libraries/utils/Lib_Bytes32Utils.sol"; import { Lib_Bytes32Utils } from "../../libraries/utils/Lib_Bytes32Utils.sol";
import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol";
/** /**
* @title OVM_ProxyEOA * @title OVM_ProxyEOA
...@@ -72,7 +72,7 @@ contract OVM_ProxyEOA { ...@@ -72,7 +72,7 @@ contract OVM_ProxyEOA {
external external
{ {
require( require(
msg.sender == address(this), msg.sender == Lib_ExecutionManagerWrapper.ovmADDRESS(),
"EOAs can only upgrade their own EOA implementation" "EOAs can only upgrade their own EOA implementation"
); );
...@@ -86,6 +86,7 @@ contract OVM_ProxyEOA { ...@@ -86,6 +86,7 @@ contract OVM_ProxyEOA {
*/ */
function getImplementation() function getImplementation()
public public
view
returns ( returns (
address address
) )
......
// SPDX-License-Identifier: MIT
// @unsupported: evm
pragma solidity >0.5.0 <0.8.0;
/* Library Imports */
import { Lib_ErrorUtils } from "../../libraries/utils/Lib_ErrorUtils.sol";
/**
* @title OVM_ExecutionManagerWrapper
* @dev This contract is a thin wrapper around the `kall` builtin. By making this contract a
* predeployed contract, we can restrict evm solc incompatibility to this one contract. Other
* contracts will typically call this contract via `Lib_ExecutionManagerWrapper`.
*
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
contract OVM_ExecutionManagerWrapper {
/*********************
* Fallback Function *
*********************/
fallback()
external
{
bytes memory data = msg.data;
assembly {
// kall is a custom yul builtin within optimistic-solc that allows us to directly call
// the execution manager (since `call` would be compiled).
kall(add(data, 0x20), mload(data), 0x0, 0x0)
// Standard returndata loading code.
let size := returndatasize()
let returndata := mload(0x40)
mstore(0x40, add(returndata, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(returndata, size)
returndatacopy(add(returndata, 0x20), 0x0, size)
// kall automatically reverts if the underlying call fails, so we only need to handle
// the success case.
return(add(returndata, 0x20), mload(returndata))
}
}
}
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// @unsupported: evm
pragma solidity >0.5.0 <0.8.0; pragma solidity >0.5.0 <0.8.0;
/* Library Imports */ /* Library Imports */
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// @unsupported: evm
pragma solidity >0.5.0 <0.8.0; pragma solidity >0.5.0 <0.8.0;
/* Library Imports */ /* Library Imports */
...@@ -7,6 +6,9 @@ import { Lib_ErrorUtils } from "../utils/Lib_ErrorUtils.sol"; ...@@ -7,6 +6,9 @@ import { Lib_ErrorUtils } from "../utils/Lib_ErrorUtils.sol";
/** /**
* @title Lib_ExecutionManagerWrapper * @title Lib_ExecutionManagerWrapper
* @dev This library acts as a utility for easily calling the OVM_ExecutionManagerWrapper, the
* predeployed contract which exposes the `kall` builtin. Effectively, this contract allows the
* user to trigger OVM opcodes by directly calling the OVM_ExecutionManger.
* *
* Compiler used: solc * Compiler used: solc
* Runtime target: OVM * Runtime target: OVM
...@@ -20,7 +22,7 @@ library Lib_ExecutionManagerWrapper { ...@@ -20,7 +22,7 @@ library Lib_ExecutionManagerWrapper {
/** /**
* Performs a safe ovmCREATE call. * Performs a safe ovmCREATE call.
* @param _bytecode Code for the new contract. * @param _bytecode Code for the new contract.
* @return _contract Address of the created contract. * @return Address of the created contract.
*/ */
function ovmCREATE( function ovmCREATE(
bytes memory _bytecode bytes memory _bytecode
...@@ -31,7 +33,7 @@ library Lib_ExecutionManagerWrapper { ...@@ -31,7 +33,7 @@ library Lib_ExecutionManagerWrapper {
bytes memory bytes memory
) )
{ {
bytes memory returndata = _safeExecutionManagerInteraction( bytes memory returndata = _callWrapperContract(
abi.encodeWithSignature( abi.encodeWithSignature(
"ovmCREATE(bytes)", "ovmCREATE(bytes)",
_bytecode _bytecode
...@@ -43,15 +45,15 @@ library Lib_ExecutionManagerWrapper { ...@@ -43,15 +45,15 @@ library Lib_ExecutionManagerWrapper {
/** /**
* Performs a safe ovmGETNONCE call. * Performs a safe ovmGETNONCE call.
* @return _nonce Result of calling ovmGETNONCE. * @return Result of calling ovmGETNONCE.
*/ */
function ovmGETNONCE() function ovmGETNONCE()
internal internal
returns ( returns (
uint256 _nonce uint256
) )
{ {
bytes memory returndata = _safeExecutionManagerInteraction( bytes memory returndata = _callWrapperContract(
abi.encodeWithSignature( abi.encodeWithSignature(
"ovmGETNONCE()" "ovmGETNONCE()"
) )
...@@ -66,7 +68,7 @@ library Lib_ExecutionManagerWrapper { ...@@ -66,7 +68,7 @@ library Lib_ExecutionManagerWrapper {
function ovmINCREMENTNONCE() function ovmINCREMENTNONCE()
internal internal
{ {
_safeExecutionManagerInteraction( _callWrapperContract(
abi.encodeWithSignature( abi.encodeWithSignature(
"ovmINCREMENTNONCE()" "ovmINCREMENTNONCE()"
) )
...@@ -88,7 +90,7 @@ library Lib_ExecutionManagerWrapper { ...@@ -88,7 +90,7 @@ library Lib_ExecutionManagerWrapper {
) )
internal internal
{ {
_safeExecutionManagerInteraction( _callWrapperContract(
abi.encodeWithSignature( abi.encodeWithSignature(
"ovmCREATEEOA(bytes32,uint8,bytes32,bytes32)", "ovmCREATEEOA(bytes32,uint8,bytes32,bytes32)",
_messageHash, _messageHash,
...@@ -109,7 +111,7 @@ library Lib_ExecutionManagerWrapper { ...@@ -109,7 +111,7 @@ library Lib_ExecutionManagerWrapper {
address address
) )
{ {
bytes memory returndata = _safeExecutionManagerInteraction( bytes memory returndata = _callWrapperContract(
abi.encodeWithSignature( abi.encodeWithSignature(
"ovmL1TXORIGIN()" "ovmL1TXORIGIN()"
) )
...@@ -128,7 +130,7 @@ library Lib_ExecutionManagerWrapper { ...@@ -128,7 +130,7 @@ library Lib_ExecutionManagerWrapper {
uint256 uint256
) )
{ {
bytes memory returndata = _safeExecutionManagerInteraction( bytes memory returndata = _callWrapperContract(
abi.encodeWithSignature( abi.encodeWithSignature(
"ovmCHAINID()" "ovmCHAINID()"
) )
...@@ -137,6 +139,25 @@ library Lib_ExecutionManagerWrapper { ...@@ -137,6 +139,25 @@ library Lib_ExecutionManagerWrapper {
return abi.decode(returndata, (uint256)); return abi.decode(returndata, (uint256));
} }
/**
* Performs a safe ovmADDRESS call.
* @return Result of calling ovmADDRESS.
*/
function ovmADDRESS()
internal
returns (
address
)
{
bytes memory returndata = _callWrapperContract(
abi.encodeWithSignature(
"ovmADDRESS()"
)
);
return abi.decode(returndata, (address));
}
/********************* /*********************
* Private Functions * * Private Functions *
...@@ -145,9 +166,9 @@ library Lib_ExecutionManagerWrapper { ...@@ -145,9 +166,9 @@ library Lib_ExecutionManagerWrapper {
/** /**
* Performs an ovm interaction and the necessary safety checks. * Performs an ovm interaction and the necessary safety checks.
* @param _calldata Data to send to the OVM_ExecutionManager (encoded with sighash). * @param _calldata Data to send to the OVM_ExecutionManager (encoded with sighash).
* @return _returndata Data sent back by the OVM_ExecutionManager. * @return Data sent back by the OVM_ExecutionManager.
*/ */
function _safeExecutionManagerInteraction( function _callWrapperContract(
bytes memory _calldata bytes memory _calldata
) )
private private
...@@ -155,17 +176,14 @@ library Lib_ExecutionManagerWrapper { ...@@ -155,17 +176,14 @@ library Lib_ExecutionManagerWrapper {
bytes memory bytes memory
) )
{ {
bytes memory returndata; (bool success, bytes memory returndata) = 0x420000000000000000000000000000000000000B.delegatecall(_calldata);
assembly {
// kall is a custom yul builtin within optimistic-solc that allows us to directly call if (success == true) {
// the execution manager (since `call` would be compiled). return returndata;
kall(add(_calldata, 0x20), mload(_calldata), 0x0, 0x0) } else {
let size := returndatasize() assembly {
returndata := mload(0x40) revert(add(returndata, 0x20), mload(returndata))
mstore(0x40, add(returndata, and(add(add(size, 0x20), 0x1f), not(0x1f)))) }
mstore(returndata, size)
returndatacopy(add(returndata, 0x20), 0x0, size)
} }
return returndata;
} }
} }
...@@ -251,5 +251,12 @@ export const makeContractDeployConfig = async ( ...@@ -251,5 +251,12 @@ export const makeContractDeployConfig = async (
OVM_ProxyEOA: { OVM_ProxyEOA: {
factory: getContractFactory('OVM_ProxyEOA', undefined, true), factory: getContractFactory('OVM_ProxyEOA', undefined, true),
}, },
OVM_ExecutionManagerWrapper: {
factory: getContractFactory(
'OVM_ExecutionManagerWrapper',
undefined,
true
),
},
} }
} }
...@@ -17,5 +17,6 @@ export const predeploys = { ...@@ -17,5 +17,6 @@ export const predeploys = {
OVM_L2CrossDomainMessenger: '0x4200000000000000000000000000000000000007', OVM_L2CrossDomainMessenger: '0x4200000000000000000000000000000000000007',
Lib_AddressManager: '0x4200000000000000000000000000000000000008', Lib_AddressManager: '0x4200000000000000000000000000000000000008',
OVM_ProxyEOA: '0x4200000000000000000000000000000000000009', OVM_ProxyEOA: '0x4200000000000000000000000000000000000009',
OVM_ExecutionManagerWrapper: '0x420000000000000000000000000000000000000B',
ERC1820Registry: '0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24', ERC1820Registry: '0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24',
} }
...@@ -136,6 +136,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => { ...@@ -136,6 +136,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
'OVM_ExecutionManager', 'OVM_ExecutionManager',
'OVM_StateManager', 'OVM_StateManager',
'OVM_ETH', 'OVM_ETH',
'OVM_ExecutionManagerWrapper',
], ],
deployOverrides: {}, deployOverrides: {},
waitForReceipts: false, waitForReceipts: false,
...@@ -152,6 +153,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => { ...@@ -152,6 +153,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
'OVM_ETH', 'OVM_ETH',
'OVM_ECDSAContractAccount', 'OVM_ECDSAContractAccount',
'OVM_ProxyEOA', 'OVM_ProxyEOA',
'OVM_ExecutionManagerWrapper',
] ]
const deploymentResult = await deploy(config) const deploymentResult = await deploy(config)
...@@ -174,7 +176,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => { ...@@ -174,7 +176,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
for (let i = 0; i < Object.keys(deploymentResult.contracts).length; i++) { for (let i = 0; i < Object.keys(deploymentResult.contracts).length; i++) {
const name = Object.keys(deploymentResult.contracts)[i] const name = Object.keys(deploymentResult.contracts)[i]
const contract = deploymentResult.contracts[name] const contract = deploymentResult.contracts[name]
let code let code: string
if (ovmCompiled.includes(name)) { if (ovmCompiled.includes(name)) {
const ovmDeployedBytecode = getContractDefinition(name, true) const ovmDeployedBytecode = getContractDefinition(name, true)
.deployedBytecode .deployedBytecode
......
...@@ -4,129 +4,49 @@ import { expect } from '../../../setup' ...@@ -4,129 +4,49 @@ import { expect } from '../../../setup'
import { ethers, waffle } from 'hardhat' import { ethers, waffle } from 'hardhat'
import { ContractFactory, Contract, Wallet, BigNumber } from 'ethers' import { ContractFactory, Contract, Wallet, BigNumber } from 'ethers'
import { MockContract, smockit } from '@eth-optimism/smock' import { MockContract, smockit } from '@eth-optimism/smock'
import { fromHexString, toHexString } from '@eth-optimism/core-utils' import { toPlainObject } from 'lodash'
/* Internal Imports */ /* Internal Imports */
import { import { DEFAULT_EIP155_TX } from '../../../helpers'
NON_ZERO_ADDRESS, import { predeploys } from '../../../../src'
DEFAULT_EIP155_TX,
decodeSolidityError,
} from '../../../helpers'
import {
getContractFactory,
getContractInterface,
predeploys,
} from '../../../../src'
const callPredeploy = async (
Helper_PredeployCaller: Contract,
predeploy: Contract,
functionName: string,
functionParams?: any[],
gasLimit?: number
): Promise<any> => {
if (gasLimit) {
return Helper_PredeployCaller.callPredeploy(
predeploy.address,
predeploy.interface.encodeFunctionData(
functionName,
functionParams || []
),
{ gasLimit }
)
}
return Helper_PredeployCaller.callPredeploy(
predeploy.address,
predeploy.interface.encodeFunctionData(functionName, functionParams || [])
)
}
const iOVM_ETH = getContractInterface('OVM_ETH')
describe('OVM_ECDSAContractAccount', () => { describe('OVM_ECDSAContractAccount', () => {
let wallet: Wallet let wallet: Wallet
let badWallet: Wallet
before(async () => { before(async () => {
const provider = waffle.provider const provider = waffle.provider
;[wallet, badWallet] = provider.getWallets() ;[wallet] = provider.getWallets()
}) })
let Mock__OVM_ExecutionManager: MockContract let Mock__OVM_ExecutionManager: MockContract
let Helper_PredeployCaller: Contract let Mock__OVM_ETH: MockContract
before(async () => { before(async () => {
Mock__OVM_ExecutionManager = await smockit( Mock__OVM_ExecutionManager = await smockit('OVM_ExecutionManager', {
await ethers.getContractFactory('OVM_ExecutionManager') address: predeploys.OVM_ExecutionManagerWrapper,
) })
Mock__OVM_ETH = await smockit('OVM_ETH', {
Helper_PredeployCaller = await ( address: predeploys.OVM_ETH,
await ethers.getContractFactory('Helper_PredeployCaller') })
).deploy()
Helper_PredeployCaller.setTarget(Mock__OVM_ExecutionManager.address)
}) })
let Factory__OVM_ECDSAContractAccount: ContractFactory let Factory__OVM_ECDSAContractAccount: ContractFactory
before(async () => { before(async () => {
Factory__OVM_ECDSAContractAccount = getContractFactory( Factory__OVM_ECDSAContractAccount = await ethers.getContractFactory(
'OVM_ECDSAContractAccount', 'OVM_ECDSAContractAccount'
wallet,
true
) )
}) })
let OVM_ECDSAContractAccount: Contract let OVM_ECDSAContractAccount: Contract
beforeEach(async () => { beforeEach(async () => {
OVM_ECDSAContractAccount = await Factory__OVM_ECDSAContractAccount.deploy() OVM_ECDSAContractAccount = await Factory__OVM_ECDSAContractAccount.deploy()
})
Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with( beforeEach(async () => {
await wallet.getAddress()
)
Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(1)
Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420) Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420)
Mock__OVM_ExecutionManager.smocked.ovmGETNONCE.will.return.with(100) Mock__OVM_ExecutionManager.smocked.ovmGETNONCE.will.return.with(100)
Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with( Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with(
(gasLimit, target, data) => { await wallet.getAddress()
if (target === predeploys.OVM_ETH) {
return [
true,
'0x0000000000000000000000000000000000000000000000000000000000000001',
]
} else {
return [true, '0x']
}
}
)
Mock__OVM_ExecutionManager.smocked.ovmSTATICCALL.will.return.with(
(gasLimit, target, data) => {
// Duplicating the behavior of the ecrecover precompile.
if (target === '0x0000000000000000000000000000000000000001') {
const databuf = fromHexString(data)
let addr: string
try {
addr = ethers.utils.recoverAddress(databuf.slice(0, 32), {
v: BigNumber.from(databuf.slice(32, 64)).toNumber(),
r: toHexString(databuf.slice(64, 96)),
s: toHexString(databuf.slice(96, 128)),
})
} catch (err) {
addr = ethers.constants.AddressZero
}
const ret = ethers.utils.defaultAbiCoder.encode(['address'], [addr])
return [true, ret]
} else {
return [true, '0x']
}
}
)
Mock__OVM_ExecutionManager.smocked.ovmCREATE.will.return.with([
NON_ZERO_ADDRESS,
'0x',
])
Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with(
NON_ZERO_ADDRESS
) )
Mock__OVM_ETH.smocked.transfer.will.return.with(true)
}) })
describe('fallback()', () => { describe('fallback()', () => {
...@@ -134,29 +54,14 @@ describe('OVM_ECDSAContractAccount', () => { ...@@ -134,29 +54,14 @@ describe('OVM_ECDSAContractAccount', () => {
const transaction = DEFAULT_EIP155_TX const transaction = DEFAULT_EIP155_TX
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await OVM_ECDSAContractAccount.execute(encodedTransaction)
Helper_PredeployCaller,
OVM_ECDSAContractAccount,
'execute',
[encodedTransaction]
)
// 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._address).to.equal(DEFAULT_EIP155_TX.to)
expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data)
}) })
it(`should ovmCREATE if EIP155Transaction.to is zero address`, async () => { it(`should ovmCREATE if EIP155Transaction.to is zero address`, async () => {
const transaction = { ...DEFAULT_EIP155_TX, to: '' } const transaction = { ...DEFAULT_EIP155_TX, to: '' }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await OVM_ECDSAContractAccount.execute(encodedTransaction)
Helper_PredeployCaller,
OVM_ECDSAContractAccount,
'execute',
[encodedTransaction]
)
const ovmCREATE: any = const ovmCREATE: any =
Mock__OVM_ExecutionManager.smocked.ovmCREATE.calls[0] Mock__OVM_ExecutionManager.smocked.ovmCREATE.calls[0]
...@@ -170,15 +75,9 @@ describe('OVM_ECDSAContractAccount', () => { ...@@ -170,15 +75,9 @@ describe('OVM_ECDSAContractAccount', () => {
'0x' + '00'.repeat(65) '0x' + '00'.repeat(65)
) )
await callPredeploy( await expect(
Helper_PredeployCaller, OVM_ECDSAContractAccount.execute(encodedTransaction)
OVM_ECDSAContractAccount, ).to.be.revertedWith(
'execute',
[encodedTransaction]
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(decodeSolidityError(ovmREVERT._data)).to.equal(
'Signature provided for EOA transaction execution is invalid.' 'Signature provided for EOA transaction execution is invalid.'
) )
}) })
...@@ -187,15 +86,9 @@ describe('OVM_ECDSAContractAccount', () => { ...@@ -187,15 +86,9 @@ describe('OVM_ECDSAContractAccount', () => {
const transaction = { ...DEFAULT_EIP155_TX, nonce: 99 } const transaction = { ...DEFAULT_EIP155_TX, nonce: 99 }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await expect(
Helper_PredeployCaller, OVM_ECDSAContractAccount.execute(encodedTransaction)
OVM_ECDSAContractAccount, ).to.be.revertedWith(
'execute',
[encodedTransaction]
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(decodeSolidityError(ovmREVERT._data)).to.equal(
'Transaction nonce does not match the expected nonce.' 'Transaction nonce does not match the expected nonce.'
) )
}) })
...@@ -204,15 +97,9 @@ describe('OVM_ECDSAContractAccount', () => { ...@@ -204,15 +97,9 @@ describe('OVM_ECDSAContractAccount', () => {
const transaction = { ...DEFAULT_EIP155_TX, chainId: 421 } const transaction = { ...DEFAULT_EIP155_TX, chainId: 421 }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await expect(
Helper_PredeployCaller, OVM_ECDSAContractAccount.execute(encodedTransaction)
OVM_ECDSAContractAccount, ).to.be.revertedWith(
'execute',
[encodedTransaction]
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(decodeSolidityError(ovmREVERT._data)).to.equal(
'Lib_EIP155Tx: Transaction signed with wrong chain ID' 'Lib_EIP155Tx: Transaction signed with wrong chain ID'
) )
}) })
...@@ -222,155 +109,72 @@ describe('OVM_ECDSAContractAccount', () => { ...@@ -222,155 +109,72 @@ describe('OVM_ECDSAContractAccount', () => {
const transaction = { ...DEFAULT_EIP155_TX, gasLimit: 200000000 } const transaction = { ...DEFAULT_EIP155_TX, gasLimit: 200000000 }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await expect(
Helper_PredeployCaller, OVM_ECDSAContractAccount.execute(encodedTransaction, {
OVM_ECDSAContractAccount, gasLimit: 40000000,
'execute', })
[encodedTransaction], ).to.be.revertedWith('Gas is not sufficient to execute the transaction.')
40000000
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(decodeSolidityError(ovmREVERT._data)).to.equal(
'Gas is not sufficient to execute the transaction.'
)
}) })
it(`should revert if fee is not transferred to the relayer`, async () => { it(`should revert if fee is not transferred to the relayer`, async () => {
const transaction = DEFAULT_EIP155_TX const transaction = DEFAULT_EIP155_TX
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with( Mock__OVM_ETH.smocked.transfer.will.return.with(false)
(gasLimit, target, data) => {
if (target === predeploys.OVM_ETH) {
return [
true,
'0x0000000000000000000000000000000000000000000000000000000000000000',
]
} else {
return [true, '0x']
}
}
)
await callPredeploy(
Helper_PredeployCaller,
OVM_ECDSAContractAccount,
'execute',
[encodedTransaction],
40000000
)
const ovmREVERT: any = await expect(
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0] OVM_ECDSAContractAccount.execute(encodedTransaction)
expect(decodeSolidityError(ovmREVERT._data)).to.equal( ).to.be.revertedWith('Fee was not transferred to relayer.')
'Fee was not transferred to relayer.'
)
}) })
it(`should transfer value if value is greater than 0`, async () => { it(`should transfer value if value is greater than 0`, async () => {
const transaction = { ...DEFAULT_EIP155_TX, value: 1234, data: '0x' } const transaction = { ...DEFAULT_EIP155_TX, value: 1234, data: '0x' }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await OVM_ECDSAContractAccount.execute(encodedTransaction)
Helper_PredeployCaller,
OVM_ECDSAContractAccount,
'execute',
[encodedTransaction],
40000000
)
// First call transfers fee, second transfers value (since value > 0). // First call transfers fee, second transfers value (since value > 0).
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1] expect(
expect(ovmCALL._address).to.equal(predeploys.OVM_ETH) toPlainObject(Mock__OVM_ETH.smocked.transfer.calls[1])
expect(ovmCALL._calldata).to.equal( ).to.deep.include({
iOVM_ETH.encodeFunctionData('transfer', [ to: transaction.to,
transaction.to, value: BigNumber.from(transaction.value),
transaction.value, })
])
)
}) })
it(`should revert if the value is not transferred to the recipient`, async () => { it(`should revert if the value is not transferred to the recipient`, async () => {
const transaction = { ...DEFAULT_EIP155_TX, value: 1234, data: '0x' } const transaction = { ...DEFAULT_EIP155_TX, value: 1234, data: '0x' }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with( Mock__OVM_ETH.smocked.transfer.will.return.with((to, value) => {
(gasLimit, target, data) => { if (to === transaction.to) {
if (target === predeploys.OVM_ETH) { return false
const [recipient, amount] = iOVM_ETH.decodeFunctionData( } else {
'transfer', return true
data
)
if (recipient === transaction.to) {
return [
true,
'0x0000000000000000000000000000000000000000000000000000000000000000',
]
} else {
return [
true,
'0x0000000000000000000000000000000000000000000000000000000000000001',
]
}
} else {
return [true, '0x']
}
} }
) })
await callPredeploy(
Helper_PredeployCaller,
OVM_ECDSAContractAccount,
'execute',
[encodedTransaction],
40000000
)
const ovmREVERT: any = await expect(
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0] OVM_ECDSAContractAccount.execute(encodedTransaction)
expect(decodeSolidityError(ovmREVERT._data)).to.equal( ).to.be.revertedWith('Value could not be transferred to recipient.')
'Value could not be transferred to recipient.'
)
}) })
it(`should revert if trying to send value with a contract creation`, async () => { it(`should revert if trying to send value with a contract creation`, async () => {
const transaction = { ...DEFAULT_EIP155_TX, value: 1234, to: '' } const transaction = { ...DEFAULT_EIP155_TX, value: 1234, to: '' }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await expect(
Helper_PredeployCaller, OVM_ECDSAContractAccount.execute(encodedTransaction)
OVM_ECDSAContractAccount, ).to.be.revertedWith('Value transfer in contract creation not supported.')
'execute',
[encodedTransaction],
40000000
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(decodeSolidityError(ovmREVERT._data)).to.equal(
'Value transfer in contract creation not supported.'
)
}) })
it(`should revert if trying to send value with non-empty transaction data`, async () => { it(`should revert if trying to send value with non-empty transaction data`, async () => {
const transaction = { ...DEFAULT_EIP155_TX, value: 1234, to: '' } const transaction = { ...DEFAULT_EIP155_TX, value: 1234, data: '0x1234' }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await callPredeploy( await expect(
Helper_PredeployCaller, OVM_ECDSAContractAccount.execute(encodedTransaction)
OVM_ECDSAContractAccount, ).to.be.revertedWith('Value is nonzero but input data was provided.')
'execute',
[encodedTransaction],
40000000
)
const ovmREVERT: any =
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(decodeSolidityError(ovmREVERT._data)).to.equal(
'Value transfer in contract creation not supported.'
)
}) })
}) })
}) })
import { expect } from '../../../setup' import { expect } from '../../../setup'
/* External Imports */ /* External Imports */
import { ethers, waffle } from 'hardhat' import { ethers } from 'hardhat'
import { ContractFactory, Contract, Wallet } from 'ethers' import { ContractFactory, Contract, Signer } from 'ethers'
import { MockContract, smockit } from '@eth-optimism/smock' import { MockContract, smockit } from '@eth-optimism/smock'
import { remove0x } from '@eth-optimism/core-utils' import { toPlainObject } from 'lodash'
/* Internal Imports */ /* Internal Imports */
import { decodeSolidityError } from '../../../helpers' import { getContractInterface, predeploys } from '../../../../src'
import { getContractInterface, getContractFactory } from '../../../../src'
const callPredeploy = async (
Helper_PredeployCaller: Contract,
predeploy: Contract,
functionName: string,
functionParams?: any[],
ethCall: boolean = false
): Promise<any> => {
if (ethCall) {
return Helper_PredeployCaller.callStatic.callPredeployAbi(
predeploy.address,
predeploy.interface.encodeFunctionData(functionName, functionParams || [])
)
}
return Helper_PredeployCaller.callPredeploy(
predeploy.address,
predeploy.interface.encodeFunctionData(functionName, functionParams || [])
)
}
const addrToBytes32 = (addr: string) => '0x' + '00'.repeat(12) + remove0x(addr)
const eoaDefaultAddr = '0x4200000000000000000000000000000000000003'
describe('OVM_ProxyEOA', () => { describe('OVM_ProxyEOA', () => {
let wallet: Wallet let signer: Signer
before(async () => { before(async () => {
const provider = waffle.provider ;[signer] = await ethers.getSigners()
;[wallet] = provider.getWallets()
}) })
let Mock__OVM_ExecutionManager: MockContract let Mock__OVM_ExecutionManager: MockContract
let Mock__OVM_ECDSAContractAccount: MockContract let Mock__OVM_ECDSAContractAccount: MockContract
let Helper_PredeployCaller: Contract
before(async () => { before(async () => {
Mock__OVM_ExecutionManager = await smockit( Mock__OVM_ExecutionManager = await smockit('OVM_ExecutionManager', {
await ethers.getContractFactory('OVM_ExecutionManager') address: predeploys.OVM_ExecutionManagerWrapper,
) })
Mock__OVM_ECDSAContractAccount = await smockit('OVM_ECDSAContractAccount', {
Helper_PredeployCaller = await ( address: predeploys.OVM_ECDSAContractAccount,
await ethers.getContractFactory('Helper_PredeployCaller') })
).deploy()
Helper_PredeployCaller.setTarget(Mock__OVM_ExecutionManager.address)
Mock__OVM_ECDSAContractAccount = await smockit(
getContractInterface('OVM_ECDSAContractAccount', true)
)
}) })
let OVM_ProxyEOAFactory: ContractFactory let Factory__OVM_ProxyEOA: ContractFactory
before(async () => { before(async () => {
OVM_ProxyEOAFactory = getContractFactory('OVM_ProxyEOA', wallet, true) Factory__OVM_ProxyEOA = await ethers.getContractFactory('OVM_ProxyEOA')
}) })
let OVM_ProxyEOA: Contract let OVM_ProxyEOA: Contract
beforeEach(async () => { beforeEach(async () => {
OVM_ProxyEOA = await OVM_ProxyEOAFactory.deploy() OVM_ProxyEOA = await Factory__OVM_ProxyEOA.deploy()
Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with(
OVM_ProxyEOA.address
)
Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with(
OVM_ProxyEOA.address
)
}) })
describe('getImplementation()', () => { describe('getImplementation()', () => {
it(`should be created with implementation at predeploy address`, async () => { it(`should be created with implementation at predeploy address`, async () => {
const eoaDefaultAddrBytes32 = addrToBytes32(eoaDefaultAddr) expect(await OVM_ProxyEOA.getImplementation()).to.equal(
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( predeploys.OVM_ECDSAContractAccount
eoaDefaultAddrBytes32
)
const implAddrBytes32 = await callPredeploy(
Helper_PredeployCaller,
OVM_ProxyEOA,
'getImplementation',
[],
true
) )
expect(implAddrBytes32).to.equal(eoaDefaultAddrBytes32)
}) })
}) })
describe('upgrade()', () => { describe('upgrade()', () => {
const implSlotKey =
'0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' //bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
it(`should upgrade the proxy implementation`, async () => { it(`should upgrade the proxy implementation`, async () => {
const newImpl = `0x${'81'.repeat(20)}` const newImpl = `0x${'81'.repeat(20)}`
const newImplBytes32 = addrToBytes32(newImpl) Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with(
await callPredeploy(Helper_PredeployCaller, OVM_ProxyEOA, 'upgrade', [ await signer.getAddress()
newImpl, )
]) await expect(OVM_ProxyEOA.upgrade(newImpl)).to.not.be.reverted
const ovmSSTORE: any = expect(await OVM_ProxyEOA.getImplementation()).to.equal(newImpl)
Mock__OVM_ExecutionManager.smocked.ovmSSTORE.calls[0]
expect(ovmSSTORE._key).to.equal(implSlotKey)
expect(ovmSSTORE._value).to.equal(newImplBytes32)
}) })
it(`should not allow upgrade of the proxy implementation by another account`, async () => { 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)}` const newImpl = `0x${'81'.repeat(20)}`
await callPredeploy(Helper_PredeployCaller, OVM_ProxyEOA, 'upgrade', [ Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with(
newImpl, ethers.constants.AddressZero
]) )
const ovmREVERT: any = await expect(OVM_ProxyEOA.upgrade(newImpl)).to.be.revertedWith(
Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0]
expect(decodeSolidityError(ovmREVERT._data)).to.equal(
'EOAs can only upgrade their own EOA implementation' 'EOAs can only upgrade their own EOA implementation'
) )
}) })
}) })
describe('fallback()', () => { describe('fallback()', () => {
it(`should call delegateCall with right calldata`, async () => { it(`should call delegateCall with right calldata`, async () => {
Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( const data = Mock__OVM_ECDSAContractAccount.interface.encodeFunctionData(
addrToBytes32(Mock__OVM_ECDSAContractAccount.address) 'execute',
['0x12341234']
) )
Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.will.return.with([
true,
'0x1234',
])
const calldata = '0xdeadbeef'
await Helper_PredeployCaller.callPredeploy(OVM_ProxyEOA.address, calldata)
const ovmDELEGATECALL: any = await signer.sendTransaction({
Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.calls[0] to: OVM_ProxyEOA.address,
expect(ovmDELEGATECALL._address).to.equal( data,
Mock__OVM_ECDSAContractAccount.address })
)
expect(ovmDELEGATECALL._calldata).to.equal(calldata) expect(
toPlainObject(Mock__OVM_ECDSAContractAccount.smocked.execute.calls[0])
).to.deep.include({
_encodedTransaction: '0x12341234',
})
}) })
it.skip(`should return data from fallback`, async () => { it.skip(`should return data from fallback`, async () => {
//TODO test return data from fallback // TODO: test return data from fallback
}) })
it.skip(`should revert in fallback`, async () => { it.skip(`should revert in fallback`, async () => {
//TODO test reversion from fallback // TODO: test reversion from fallback
}) })
}) })
}) })
...@@ -2,154 +2,113 @@ import { expect } from '../../../setup' ...@@ -2,154 +2,113 @@ import { expect } from '../../../setup'
/* External Imports */ /* External Imports */
import { waffle, ethers } from 'hardhat' import { waffle, ethers } from 'hardhat'
import { ContractFactory, Wallet, Contract, BigNumber } from 'ethers' import { ContractFactory, Wallet, Contract, Signer } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock' import { smockit, MockContract, unbind } from '@eth-optimism/smock'
import { fromHexString, toHexString } from '@eth-optimism/core-utils' import { toPlainObject } from 'lodash'
/* Internal Imports */ /* Internal Imports */
import { DEFAULT_EIP155_TX } from '../../../helpers' import { DEFAULT_EIP155_TX } from '../../../helpers'
import { getContractInterface, getContractFactory } from '../../../../src' import { getContractInterface, predeploys } from '../../../../src'
describe('OVM_SequencerEntrypoint', () => { describe('OVM_SequencerEntrypoint', () => {
const iOVM_ECDSAContractAccount = getContractInterface(
'OVM_ECDSAContractAccount'
)
let wallet: Wallet let wallet: Wallet
before(async () => { before(async () => {
const provider = waffle.provider const provider = waffle.provider
;[wallet] = provider.getWallets() ;[wallet] = provider.getWallets()
}) })
let signer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
})
let Mock__OVM_ExecutionManager: MockContract let Mock__OVM_ExecutionManager: MockContract
let Helper_PredeployCaller: Contract
before(async () => { before(async () => {
Mock__OVM_ExecutionManager = await smockit( Mock__OVM_ExecutionManager = await smockit('OVM_ExecutionManager', {
await ethers.getContractFactory('OVM_ExecutionManager') address: predeploys.OVM_ExecutionManagerWrapper,
) })
Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420) Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420)
Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with( Mock__OVM_ExecutionManager.smocked.ovmCREATEEOA.will.return()
(gasLimit, target, data) => {
if (target === wallet.address) {
return [
true,
iOVM_ECDSAContractAccount.encodeFunctionResult('execute', [
true,
'0x',
]),
]
} else {
return [true, '0x']
}
}
)
Mock__OVM_ExecutionManager.smocked.ovmSTATICCALL.will.return.with(
(gasLimit, target, data) => {
// Duplicating the behavior of the ecrecover precompile.
if (target === '0x0000000000000000000000000000000000000001') {
const databuf = fromHexString(data)
const addr = ethers.utils.recoverAddress(databuf.slice(0, 32), {
v: BigNumber.from(databuf.slice(32, 64)).toNumber(),
r: toHexString(databuf.slice(64, 96)),
s: toHexString(databuf.slice(96, 128)),
})
const ret = ethers.utils.defaultAbiCoder.encode(['address'], [addr])
return [true, ret]
} else {
return [true, '0x']
}
}
)
Helper_PredeployCaller = await (
await ethers.getContractFactory('Helper_PredeployCaller')
).deploy()
Helper_PredeployCaller.setTarget(Mock__OVM_ExecutionManager.address)
}) })
let OVM_SequencerEntrypointFactory: ContractFactory let Factory__OVM_SequencerEntrypoint: ContractFactory
before(async () => { before(async () => {
OVM_SequencerEntrypointFactory = getContractFactory( Factory__OVM_SequencerEntrypoint = await ethers.getContractFactory(
'OVM_SequencerEntrypoint', 'OVM_SequencerEntrypoint'
wallet,
true
) )
}) })
const iOVM_ECDSAContractAccount = getContractInterface(
'OVM_ECDSAContractAccount',
true
)
let OVM_SequencerEntrypoint: Contract let OVM_SequencerEntrypoint: Contract
beforeEach(async () => { beforeEach(async () => {
OVM_SequencerEntrypoint = await OVM_SequencerEntrypointFactory.deploy() OVM_SequencerEntrypoint = await Factory__OVM_SequencerEntrypoint.deploy()
Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(1)
Mock__OVM_ExecutionManager.smocked.ovmREVERT.will.revert()
}) })
describe('fallback()', async () => { describe('fallback()', async () => {
it('should call EIP155', async () => { it('should call ovmCREATEEOA when ovmEXTCODESIZE returns 0', async () => {
const transaction = DEFAULT_EIP155_TX const transaction = DEFAULT_EIP155_TX
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await Helper_PredeployCaller.callPredeploy( // Just unbind the smock in case it's there during this test for some reason.
OVM_SequencerEntrypoint.address, await unbind(await wallet.getAddress())
encodedTransaction
) await signer.sendTransaction({
to: OVM_SequencerEntrypoint.address,
const expectedEOACalldata = iOVM_ECDSAContractAccount.encodeFunctionData( data: encodedTransaction,
'execute', })
[encodedTransaction]
) const call: any = Mock__OVM_ExecutionManager.smocked.ovmCREATEEOA.calls[0]
const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0] const eoaAddress = ethers.utils.recoverAddress(call._messageHash, {
expect(ovmCALL._address).to.equal(await wallet.getAddress()) v: call._v + 27,
expect(ovmCALL._calldata).to.equal(expectedEOACalldata) r: call._r,
s: call._s,
})
expect(eoaAddress).to.equal(await wallet.getAddress())
}) })
it('should send correct calldata if tx is a create', async () => { it('should call EIP155', async () => {
const transaction = { ...DEFAULT_EIP155_TX, to: '' } const transaction = DEFAULT_EIP155_TX
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await Helper_PredeployCaller.callPredeploy( const Mock__wallet = await smockit(iOVM_ECDSAContractAccount, {
OVM_SequencerEntrypoint.address, address: await wallet.getAddress(),
encodedTransaction })
)
const expectedEOACalldata = iOVM_ECDSAContractAccount.encodeFunctionData(
'execute',
[encodedTransaction]
)
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 call ovmCreateEOA when ovmEXTCODESIZE returns 0`, async () => { await signer.sendTransaction({
let isFirstCheck = true to: OVM_SequencerEntrypoint.address,
Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(() => { data: encodedTransaction,
if (isFirstCheck) {
isFirstCheck = false
return 0
} else {
return 1
}
}) })
const transaction = DEFAULT_EIP155_TX expect(
toPlainObject(Mock__wallet.smocked.execute.calls[0])
).to.deep.include({
_encodedTransaction: encodedTransaction,
})
})
it('should send correct calldata if tx is a create', async () => {
const transaction = { ...DEFAULT_EIP155_TX, to: '' }
const encodedTransaction = await wallet.signTransaction(transaction) const encodedTransaction = await wallet.signTransaction(transaction)
await Helper_PredeployCaller.callPredeploy( const Mock__wallet = await smockit(iOVM_ECDSAContractAccount, {
OVM_SequencerEntrypoint.address, address: await wallet.getAddress(),
encodedTransaction })
)
const call: any = Mock__OVM_ExecutionManager.smocked.ovmCREATEEOA.calls[0] await signer.sendTransaction({
const eoaAddress = ethers.utils.recoverAddress(call._messageHash, { to: OVM_SequencerEntrypoint.address,
v: call._v + 27, data: encodedTransaction,
r: call._r,
s: call._s,
}) })
expect(eoaAddress).to.equal(await wallet.getAddress()) expect(
toPlainObject(Mock__wallet.smocked.execute.calls[0])
).to.deep.include({
_encodedTransaction: encodedTransaction,
})
}) })
}) })
}) })
...@@ -63,7 +63,12 @@ const initializeSmock = (provider: HardhatNetworkProvider): void => { ...@@ -63,7 +63,12 @@ const initializeSmock = (provider: HardhatNetworkProvider): void => {
return return
} }
const target = fromFancyAddress(message.to) let target: string
if (message.delegatecall) {
target = fromFancyAddress(message._codeAddress)
} else {
target = fromFancyAddress(message.to)
}
// Check if the target address is a smocked contract. // Check if the target address is a smocked contract.
if (!(target in vm._smockState.mocks)) { if (!(target in vm._smockState.mocks)) {
...@@ -103,7 +108,13 @@ const initializeSmock = (provider: HardhatNetworkProvider): void => { ...@@ -103,7 +108,13 @@ const initializeSmock = (provider: HardhatNetworkProvider): void => {
// contracts never create new sub-calls (meaning this `afterMessage` event corresponds directly // contracts never create new sub-calls (meaning this `afterMessage` event corresponds directly
// to a `beforeMessage` event emitted during a call to a smock contract). // to a `beforeMessage` event emitted during a call to a smock contract).
const message = vm._smockState.messages.pop() const message = vm._smockState.messages.pop()
const target = fromFancyAddress(message.to)
let target: string
if (message.delegatecall) {
target = fromFancyAddress(message._codeAddress)
} else {
target = fromFancyAddress(message.to)
}
// Not sure if this can ever actually happen? Just being safe. // Not sure if this can ever actually happen? Just being safe.
if (!(target in vm._smockState.mocks)) { if (!(target in vm._smockState.mocks)) {
...@@ -181,3 +192,32 @@ export const bindSmock = async ( ...@@ -181,3 +192,32 @@ export const bindSmock = async (
Buffer.from('00', 'hex') Buffer.from('00', 'hex')
) )
} }
/**
* Detaches a smocked contract from a hardhat network provider.
* @param mock Smocked contract to detach to a provider, or an address.
* @param provider Hardhat network provider to detatch the contract from.
*/
export const unbindSmock = async (
mock: MockContract | string,
provider: HardhatNetworkProvider
): Promise<void> => {
if (!isSmockInitialized(provider)) {
initializeSmock(provider)
}
const vm: SmockedVM = (provider as any)._node._vm
const pStateManager = vm.pStateManager || vm.stateManager
// Add mock to our list of mocks currently attached to the VM.
const address = typeof mock === 'string' ? mock : mock.address.toLowerCase()
delete vm._smockState.mocks[address]
// Set the contract code for our mock to 0x00 == STOP. Need some non-empty contract code because
// Solidity will sometimes throw if it's calling something without code (I forget the exact
// scenario that causes this throw).
await pStateManager.putContractCode(
toFancyAddress(address),
Buffer.from('', 'hex')
)
}
...@@ -16,7 +16,7 @@ import { ...@@ -16,7 +16,7 @@ import {
SmockOptions, SmockOptions,
SmockSpec, SmockSpec,
} from './types' } from './types'
import { bindSmock } from './binding' import { bindSmock, unbindSmock } from './binding'
import { makeRandomAddress } from '../utils' import { makeRandomAddress } from '../utils'
import { findBaseHardhatProvider } from '../common' import { findBaseHardhatProvider } from '../common'
...@@ -304,3 +304,22 @@ export const smockit = async ( ...@@ -304,3 +304,22 @@ export const smockit = async (
return contract return contract
} }
/**
* Unbinds a mock contract (meaning the contract will no longer behave as a mock).
* @param mock Mock contract or address to unbind.
*/
export const unbind = async (mock: MockContract | string): Promise<void> => {
// Only support native hardhat runtime, haven't bothered to figure it out for anything else.
if (hre.network.name !== 'hardhat') {
throw new Error(
`[smock]: smock is only compatible with the "hardhat" network, got: ${hre.network.name}`
)
}
// Find the provider object. See comments for `findBaseHardhatProvider`
const provider = findBaseHardhatProvider(hre)
// Unbind the contract.
await unbindSmock(mock, provider)
}
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