Commit 0f8bac35 authored by Georgios Konstantopoulos's avatar Georgios Konstantopoulos Committed by GitHub

chore: update smock and contracts to latest master (#52)

* fix(smock): update to latest master

https://github.com/ethereum-optimism/smock/pull/43
https://github.com/ethereum-optimism/smock/pull/40
https://github.com/ethereum-optimism/smock/pull/37
https://github.com/ethereum-optimism/smock/pull/36

* fix(contracts): revert more rigorously for unsafe bytecode

https://github.com/ethereum-optimism/contracts/pull/369

* fix(contracts): prevent re-entrancy by disallowing default context values

https://github.com/ethereum-optimism/contracts/pull/363

* test(contracts): improve coverage for StateTransitionerFactory

https://github.com/ethereum-optimism/contracts/pull/365

* chore(contracts): cleanup libraries

* fix: MAX_ROLLUP_TX_SIZE = 50k

* test: fix batch submitter scc import path

* fix(contracts): set correct nuisance gas

We get this by inspecting the EM.messageRecord.nuisanceGasLeft storage slot manually, would be nice
if we could automatically calculate this
parent 0746c1ac
...@@ -7,8 +7,9 @@ import { Signer, ContractFactory, Contract, BigNumber } from 'ethers' ...@@ -7,8 +7,9 @@ import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import ganache from 'ganache-core' import ganache from 'ganache-core'
import sinon from 'sinon' import sinon from 'sinon'
import { Web3Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
import scc from '@eth-optimism/contracts/artifacts/contracts/optimistic-ethereum/OVM/chain/OVM_StateCommitmentChain.sol/OVM_StateCommitmentChain.json'
import { getContractInterface } from '@eth-optimism/contracts' import { getContractInterface } from '@eth-optimism/contracts'
import * as scc from '@eth-optimism/contracts/dist/artifacts/contracts/optimistic-ethereum/OVM/chain/OVM_StateCommitmentChain.sol/OVM_StateCommitmentChain.json'
import { smockit, MockContract } from '@eth-optimism/smock' import { smockit, MockContract } from '@eth-optimism/smock'
/* Internal Imports */ /* Internal Imports */
......
...@@ -38,7 +38,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad ...@@ -38,7 +38,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
// L2 tx gas-related // L2 tx gas-related
uint256 constant public MIN_ROLLUP_TX_GAS = 100000; uint256 constant public MIN_ROLLUP_TX_GAS = 100000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 10000; uint256 constant public MAX_ROLLUP_TX_SIZE = 50000;
uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 32; uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 32;
// Encoding-related (all in bytes) // Encoding-related (all in bytes)
......
...@@ -23,15 +23,20 @@ import { OVM_StateTransitioner } from "./OVM_StateTransitioner.sol"; ...@@ -23,15 +23,20 @@ import { OVM_StateTransitioner } from "./OVM_StateTransitioner.sol";
*/ */
contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory, Lib_AddressResolver { contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory, Lib_AddressResolver {
/***************
* Constructor *
***************/
constructor( constructor(
address _libAddressManager address _libAddressManager
) )
Lib_AddressResolver(_libAddressManager) Lib_AddressResolver(_libAddressManager)
{} {}
/***************************************
* Public Functions: Contract Creation * /********************
***************************************/ * Public Functions *
********************/
/** /**
* Creates a new OVM_StateTransitioner * Creates a new OVM_StateTransitioner
...@@ -39,7 +44,7 @@ contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory, Lib_Addr ...@@ -39,7 +44,7 @@ contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory, Lib_Addr
* @param _stateTransitionIndex Index of the state transition being verified. * @param _stateTransitionIndex Index of the state transition being verified.
* @param _preStateRoot State root before the transition was executed. * @param _preStateRoot State root before the transition was executed.
* @param _transactionHash Hash of the executed transaction. * @param _transactionHash Hash of the executed transaction.
* @return _ovmStateTransitioner New OVM_StateTransitioner instance. * @return New OVM_StateTransitioner instance.
*/ */
function create( function create(
address _libAddressManager, address _libAddressManager,
...@@ -50,13 +55,14 @@ contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory, Lib_Addr ...@@ -50,13 +55,14 @@ contract OVM_StateTransitionerFactory is iOVM_StateTransitionerFactory, Lib_Addr
override override
public public
returns ( returns (
iOVM_StateTransitioner _ovmStateTransitioner iOVM_StateTransitioner
) )
{ {
require( require(
msg.sender == resolve("OVM_FraudVerifier"), msg.sender == resolve("OVM_FraudVerifier"),
"Create can only be done by the OVM_FraudVerifier." "Create can only be done by the OVM_FraudVerifier."
); );
return new OVM_StateTransitioner( return new OVM_StateTransitioner(
_libAddressManager, _libAddressManager,
_stateTransitionIndex, _stateTransitionIndex,
......
...@@ -18,9 +18,10 @@ contract Lib_AddressManager is Ownable { ...@@ -18,9 +18,10 @@ contract Lib_AddressManager is Ownable {
address _newAddress address _newAddress
); );
/*******************************************
* Contract Variables: Internal Accounting * /*************
*******************************************/ * Variables *
*************/
mapping (bytes32 => address) private addresses; mapping (bytes32 => address) private addresses;
...@@ -29,6 +30,11 @@ contract Lib_AddressManager is Ownable { ...@@ -29,6 +30,11 @@ contract Lib_AddressManager is Ownable {
* Public Functions * * Public Functions *
********************/ ********************/
/**
* Changes the address associated with a particular name.
* @param _name String name to associate an address with.
* @param _address Address to associate with the name.
*/
function setAddress( function setAddress(
string memory _name, string memory _name,
address _address address _address
...@@ -36,16 +42,27 @@ contract Lib_AddressManager is Ownable { ...@@ -36,16 +42,27 @@ contract Lib_AddressManager is Ownable {
public public
onlyOwner onlyOwner
{ {
emit AddressSet(_name, _address);
addresses[_getNameHash(_name)] = _address; addresses[_getNameHash(_name)] = _address;
emit AddressSet(
_name,
_address
);
} }
/**
* Retrieves the address associated with a given name.
* @param _name Name to retrieve an address for.
* @return Address associated with the given name.
*/
function getAddress( function getAddress(
string memory _name string memory _name
) )
public public
view view
returns (address) returns (
address
)
{ {
return addresses[_getNameHash(_name)]; return addresses[_getNameHash(_name)];
} }
...@@ -55,13 +72,18 @@ contract Lib_AddressManager is Ownable { ...@@ -55,13 +72,18 @@ contract Lib_AddressManager is Ownable {
* Internal Functions * * Internal Functions *
**********************/ **********************/
/**
* Computes the hash of a name.
* @param _name Name to compute a hash for.
* @return Hash of the given name.
*/
function _getNameHash( function _getNameHash(
string memory _name string memory _name
) )
internal internal
pure pure
returns ( returns (
bytes32 _hash bytes32
) )
{ {
return keccak256(abi.encodePacked(_name)); return keccak256(abi.encodePacked(_name));
......
...@@ -9,9 +9,9 @@ import { Lib_AddressManager } from "./Lib_AddressManager.sol"; ...@@ -9,9 +9,9 @@ import { Lib_AddressManager } from "./Lib_AddressManager.sol";
*/ */
abstract contract Lib_AddressResolver { abstract contract Lib_AddressResolver {
/******************************************* /*************
* Contract Variables: Contract References * * Variables *
*******************************************/ *************/
Lib_AddressManager public libAddressManager; Lib_AddressManager public libAddressManager;
...@@ -25,7 +25,7 @@ abstract contract Lib_AddressResolver { ...@@ -25,7 +25,7 @@ abstract contract Lib_AddressResolver {
*/ */
constructor( constructor(
address _libAddressManager address _libAddressManager
) { ) {
libAddressManager = Lib_AddressManager(_libAddressManager); libAddressManager = Lib_AddressManager(_libAddressManager);
} }
...@@ -34,13 +34,18 @@ abstract contract Lib_AddressResolver { ...@@ -34,13 +34,18 @@ abstract contract Lib_AddressResolver {
* Public Functions * * Public Functions *
********************/ ********************/
/**
* Resolves the address associated with a given name.
* @param _name Name to resolve an address for.
* @return Address associated with the given name.
*/
function resolve( function resolve(
string memory _name string memory _name
) )
public public
view view
returns ( returns (
address _contract address
) )
{ {
return libAddressManager.getAddress(_name); return libAddressManager.getAddress(_name);
......
...@@ -51,6 +51,10 @@ abstract contract Ownable { ...@@ -51,6 +51,10 @@ abstract contract Ownable {
* Public Functions * * Public Functions *
********************/ ********************/
/**
* Sets the owner of this contract to the zero address, effectively renouncing ownership
* completely. Can only be called by the current owner of this contract.
*/
function renounceOwnership() function renounceOwnership()
public public
virtual virtual
...@@ -60,7 +64,14 @@ abstract contract Ownable { ...@@ -60,7 +64,14 @@ abstract contract Ownable {
owner = address(0); owner = address(0);
} }
function transferOwnership(address _newOwner) /**
* Transfers ownership of this contract to a new address. Can only be called by the current
* owner of this contract.
* @param _newOwner Address of the new contract owner.
*/
function transferOwnership(
address _newOwner
)
public public
virtual virtual
onlyOwner onlyOwner
......
...@@ -13,7 +13,6 @@ contract Lib_ResolvedDelegateProxy { ...@@ -13,7 +13,6 @@ contract Lib_ResolvedDelegateProxy {
* Variables * * Variables *
*************/ *************/
// Using mappings to store fields to avoid overwriting storage slots in the // Using mappings to store fields to avoid overwriting storage slots in the
// implementation contract. For example, instead of storing these fields at // implementation contract. For example, instead of storing these fields at
// storage slot `0` & `1`, they are stored at `keccak256(key + slot)`. // storage slot `0` & `1`, they are stored at `keccak256(key + slot)`.
...@@ -22,8 +21,8 @@ contract Lib_ResolvedDelegateProxy { ...@@ -22,8 +21,8 @@ contract Lib_ResolvedDelegateProxy {
// There is a known flaw in this contract, and we will remove it from the repository // There is a known flaw in this contract, and we will remove it from the repository
// in the near future. Due to the very limited way that we are using it, this flaw is // in the near future. Due to the very limited way that we are using it, this flaw is
// not an issue in our system. // not an issue in our system.
mapping(address=>string) private implementationName; mapping (address => string) private implementationName;
mapping(address=>Lib_AddressManager) private addressManager; mapping (address => Lib_AddressManager) private addressManager;
/*************** /***************
...@@ -52,7 +51,10 @@ contract Lib_ResolvedDelegateProxy { ...@@ -52,7 +51,10 @@ contract Lib_ResolvedDelegateProxy {
external external
payable payable
{ {
address target = addressManager[address(this)].getAddress((implementationName[address(this)])); address target = addressManager[address(this)].getAddress(
(implementationName[address(this)])
);
require( require(
target != address(0), target != address(0),
"Target address must be initialized." "Target address must be initialized."
......
...@@ -36,7 +36,7 @@ describe('OVM_L1ERC20Gateway', () => { ...@@ -36,7 +36,7 @@ describe('OVM_L1ERC20Gateway', () => {
L1ERC20 = await Factory__L1ERC20.deploy('L1ERC20', 'ERC') L1ERC20 = await Factory__L1ERC20.deploy('L1ERC20', 'ERC')
const aliceAddress = await alice.getAddress() const aliceAddress = await alice.getAddress()
L1ERC20.smodify.put({ await L1ERC20.smodify.put({
totalSupply: INITIAL_TOTAL_L1_SUPPLY, totalSupply: INITIAL_TOTAL_L1_SUPPLY,
balanceOf: { balanceOf: {
[aliceAddress]: INITIAL_TOTAL_L1_SUPPLY, [aliceAddress]: INITIAL_TOTAL_L1_SUPPLY,
......
...@@ -188,7 +188,9 @@ describe('OVM_CanonicalTransactionChain', () => { ...@@ -188,7 +188,9 @@ describe('OVM_CanonicalTransactionChain', () => {
const data = '0x' + '12'.repeat(MAX_ROLLUP_TX_SIZE + 1) const data = '0x' + '12'.repeat(MAX_ROLLUP_TX_SIZE + 1)
await expect( await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data) OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data, {
gasLimit: 40000000,
})
).to.be.revertedWith( ).to.be.revertedWith(
'Transaction data size exceeds maximum for rollup transaction.' 'Transaction data size exceeds maximum for rollup transaction.'
) )
......
...@@ -110,7 +110,7 @@ describe('OVM_ExecutionManager gas consumption', () => { ...@@ -110,7 +110,7 @@ describe('OVM_ExecutionManager gas consumption', () => {
) )
console.log(`calculated gas cost of ${gasCost}`) console.log(`calculated gas cost of ${gasCost}`)
const benchmark: number = 225_500 const benchmark: number = 105_000
expect(gasCost).to.be.lte(benchmark) expect(gasCost).to.be.lte(benchmark)
expect(gasCost).to.be.gte( expect(gasCost).to.be.gte(
benchmark - 1_000, benchmark - 1_000,
......
...@@ -188,7 +188,7 @@ const test_nuisanceGas: TestDefinition = { ...@@ -188,7 +188,7 @@ const test_nuisanceGas: TestDefinition = {
// This is because there is natural gas consumption between the ovmCALL(GAS/2) and ovmCREATE, which allots nuisance gas via _getNuisanceGasLimit. // This is because there is natural gas consumption between the ovmCALL(GAS/2) and ovmCREATE, which allots nuisance gas via _getNuisanceGasLimit.
// This means that the ovmCREATE exception, DOES consumes all nuisance gas allotted, but that allotment // This means that the ovmCREATE exception, DOES consumes all nuisance gas allotted, but that allotment
// is less than the full OVM_TX_GAS_LIMIT / 2 which is alloted to the parent ovmCALL. // is less than the full OVM_TX_GAS_LIMIT / 2 which is alloted to the parent ovmCALL.
nuisanceGasLeft: 4531286, nuisanceGasLeft: 4184829,
}, },
}, },
}, },
......
...@@ -212,7 +212,7 @@ describe('OVM_BondManager', () => { ...@@ -212,7 +212,7 @@ describe('OVM_BondManager', () => {
// smodify the canClaim value to true to test claiming // smodify the canClaim value to true to test claiming
const block = await provider.getBlock('latest') const block = await provider.getBlock('latest')
timestamp = block.timestamp timestamp = block.timestamp
bondManager.smodify.set({ await bondManager.smodify.put({
witnessProviders: { witnessProviders: {
[preStateRoot]: { [preStateRoot]: {
canClaim: true, canClaim: true,
......
...@@ -312,7 +312,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -312,7 +312,7 @@ describe('OVM_StateTransitioner', () => {
) )
) )
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 0, phase: 0,
transactionHash, transactionHash,
}) })
...@@ -329,7 +329,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -329,7 +329,7 @@ describe('OVM_StateTransitioner', () => {
describe('commitContractState', () => { describe('commitContractState', () => {
beforeEach(async () => { beforeEach(async () => {
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 1, phase: 1,
}) })
}) })
...@@ -394,7 +394,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -394,7 +394,7 @@ describe('OVM_StateTransitioner', () => {
proof = test.accountTrieWitness proof = test.accountTrieWitness
postStateRoot = test.newAccountTrieRoot postStateRoot = test.newAccountTrieRoot
OVM_StateTransitioner.smodify.put({ await OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot, postStateRoot: test.accountTrieRoot,
}) })
}) })
...@@ -413,8 +413,8 @@ describe('OVM_StateTransitioner', () => { ...@@ -413,8 +413,8 @@ describe('OVM_StateTransitioner', () => {
}) })
describe('commitStorageSlot', () => { describe('commitStorageSlot', () => {
beforeEach(() => { beforeEach(async () => {
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 1, phase: 1,
}) })
}) })
...@@ -510,7 +510,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -510,7 +510,7 @@ describe('OVM_StateTransitioner', () => {
storageTrieProof = storageTest.proof storageTrieProof = storageTest.proof
OVM_StateTransitioner.smodify.put({ await OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot, postStateRoot: test.accountTrieRoot,
}) })
}) })
...@@ -529,8 +529,8 @@ describe('OVM_StateTransitioner', () => { ...@@ -529,8 +529,8 @@ describe('OVM_StateTransitioner', () => {
}) })
describe('completeTransition', () => { describe('completeTransition', () => {
beforeEach(() => { beforeEach(async () => {
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 1, phase: 1,
}) })
}) })
......
...@@ -2,7 +2,8 @@ import { expect } from '../../../setup' ...@@ -2,7 +2,8 @@ import { expect } from '../../../setup'
/* External Imports */ /* External Imports */
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import { ContractFactory, Contract, constants } from 'ethers' import { ContractFactory, Contract, constants, Signer } from 'ethers'
import { MockContract, smockit } from '@eth-optimism/smock'
/* Internal Imports */ /* Internal Imports */
import { import {
...@@ -14,6 +15,11 @@ import { ...@@ -14,6 +15,11 @@ import {
const DUMMY_HASH = hashTransaction(DUMMY_OVM_TRANSACTIONS[0]) const DUMMY_HASH = hashTransaction(DUMMY_OVM_TRANSACTIONS[0])
describe('OVM_StateTransitionerFactory', () => { describe('OVM_StateTransitionerFactory', () => {
let signer1: Signer
before(async () => {
;[signer1] = await ethers.getSigners()
})
let AddressManager: Contract let AddressManager: Contract
before(async () => { before(async () => {
AddressManager = await makeAddressManager() AddressManager = await makeAddressManager()
...@@ -27,15 +33,26 @@ describe('OVM_StateTransitionerFactory', () => { ...@@ -27,15 +33,26 @@ describe('OVM_StateTransitionerFactory', () => {
}) })
let OVM_StateTransitionerFactory: Contract let OVM_StateTransitionerFactory: Contract
let Mock__OVM_StateManagerFactory: MockContract
beforeEach(async () => { beforeEach(async () => {
OVM_StateTransitionerFactory = await Factory__OVM_StateTransitionerFactory.deploy( OVM_StateTransitionerFactory = await Factory__OVM_StateTransitionerFactory.deploy(
AddressManager.address AddressManager.address
) )
Mock__OVM_StateManagerFactory = await smockit('OVM_StateManagerFactory')
Mock__OVM_StateManagerFactory.smocked.create.will.return.with(
ethers.constants.AddressZero
)
await AddressManager.setAddress(
'OVM_StateManagerFactory',
Mock__OVM_StateManagerFactory.address
)
}) })
describe('create', () => { describe('create', () => {
describe('when the sender is not the OVM_FraudVerifier', () => { describe('when the sender is not the OVM_FraudVerifier', () => {
before(async () => { beforeEach(async () => {
await AddressManager.setAddress( await AddressManager.setAddress(
'OVM_FraudVerifier', 'OVM_FraudVerifier',
constants.AddressZero constants.AddressZero
...@@ -55,5 +72,25 @@ describe('OVM_StateTransitionerFactory', () => { ...@@ -55,5 +72,25 @@ describe('OVM_StateTransitionerFactory', () => {
) )
}) })
}) })
describe('when the sender is the OVM_FraudVerifier', () => {
beforeEach(async () => {
await AddressManager.setAddress(
'OVM_FraudVerifier',
await signer1.getAddress()
)
})
it('should not revert', async () => {
await expect(
OVM_StateTransitionerFactory.connect(signer1).create(
AddressManager.address,
ethers.constants.HashZero,
ethers.constants.HashZero,
DUMMY_HASH
)
).to.not.be.reverted
})
})
}) })
}) })
...@@ -4,6 +4,9 @@ import { defaultAccounts } from 'ethereum-waffle' ...@@ -4,6 +4,9 @@ import { defaultAccounts } from 'ethereum-waffle'
import { fromHexString, toHexString } from '@eth-optimism/core-utils' import { fromHexString, toHexString } from '@eth-optimism/core-utils'
import xor from 'buffer-xor' import xor from 'buffer-xor'
/* Internal Imports */
import { getContractDefinition } from '../../src'
export const DEFAULT_ACCOUNTS = defaultAccounts export const DEFAULT_ACCOUNTS = defaultAccounts
export const DEFAULT_ACCOUNTS_HARDHAT = defaultAccounts.map((account) => { export const DEFAULT_ACCOUNTS_HARDHAT = defaultAccounts.map((account) => {
return { return {
...@@ -35,8 +38,17 @@ export const NUISANCE_GAS_COSTS = { ...@@ -35,8 +38,17 @@ export const NUISANCE_GAS_COSTS = {
MIN_GAS_FOR_INVALID_STATE_ACCESS: 30000, MIN_GAS_FOR_INVALID_STATE_ACCESS: 30000,
} }
// TODO: get this exported/imported somehow in a way that we can do math on it. unfortunately using require('.....artifacts/contract.json') is erroring... let len
export const Helper_TestRunner_BYTELEN = 3654 // This is hacky, but `hardhat compile` evaluates this file for some reason.
// Feels better to have something hacky then a constant we have to keep re-hardcoding.
try {
len = fromHexString(
getContractDefinition('Helper_TestRunner').deployedBytecode
).byteLength
/* tslint:disable:no-empty */
} catch {}
export const Helper_TestRunner_BYTELEN = len
export const STORAGE_XOR = export const STORAGE_XOR =
'0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef' '0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef'
......
...@@ -79,6 +79,11 @@ export class ExecutionManagerTestRunner { ...@@ -79,6 +79,11 @@ export class ExecutionManagerTestRunner {
}, },
}, },
}, },
ExecutionManager: {
transactionRecord: {
ovmGasRefund: 0,
},
},
} }
public run(test: TestDefinition) { public run(test: TestDefinition) {
...@@ -116,8 +121,8 @@ export class ExecutionManagerTestRunner { ...@@ -116,8 +121,8 @@ export class ExecutionManagerTestRunner {
replacedParameter = this.setPlaceholderStrings(parameter) replacedParameter = this.setPlaceholderStrings(parameter)
}) })
beforeEach(() => { beforeEach(async () => {
this.contracts.OVM_StateManager.smodify.set({ await this.contracts.OVM_StateManager.smodify.put({
accounts: { accounts: {
[this.contracts.Helper_TestRunner.address]: { [this.contracts.Helper_TestRunner.address]: {
nonce: 0, nonce: 0,
...@@ -128,11 +133,11 @@ export class ExecutionManagerTestRunner { ...@@ -128,11 +133,11 @@ export class ExecutionManagerTestRunner {
}) })
}) })
beforeEach(() => { beforeEach(async () => {
this.contracts.OVM_ExecutionManager.smodify.set( await this.contracts.OVM_ExecutionManager.smodify.put(
replacedTest.preState.ExecutionManager replacedTest.preState.ExecutionManager
) )
this.contracts.OVM_StateManager.smodify.set( await this.contracts.OVM_StateManager.smodify.put(
replacedTest.preState.StateManager replacedTest.preState.StateManager
) )
}) })
......
...@@ -168,9 +168,7 @@ interface ModifiableContractFactory extends ContractFactory { ...@@ -168,9 +168,7 @@ interface ModifiableContractFactory extends ContractFactory {
```typescript ```typescript
interface ModifiableContract extends Contract { interface ModifiableContract extends Contract {
smodify: { smodify: {
put: (storage: any) => void put: (storage: any) => Promise<void>
set: (storage: any) => void
reset: () => void
} }
} }
``` ```
...@@ -362,7 +360,7 @@ import { smoddit } from '@eth-optimism/smock' ...@@ -362,7 +360,7 @@ import { smoddit } from '@eth-optimism/smock'
const MyModifiableContractFactory = await smoddit('MyContract') const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...) const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({ await MyModifiableContract.smodify.put({
myInternalUint256: 1234 myInternalUint256: 1234
}) })
...@@ -378,7 +376,7 @@ import { smoddit } from '@eth-optimism/smock' ...@@ -378,7 +376,7 @@ import { smoddit } from '@eth-optimism/smock'
const MyModifiableContractFactory = await smoddit('MyContract') const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...) const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({ await MyModifiableContract.smodify.put({
myInternalStruct: { myInternalStruct: {
valueA: 1234, valueA: 1234,
valueB: true valueB: true
...@@ -397,7 +395,7 @@ import { smoddit } from '@eth-optimism/smock' ...@@ -397,7 +395,7 @@ import { smoddit } from '@eth-optimism/smock'
const MyModifiableContractFactory = await smoddit('MyContract') const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...) const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({ await MyModifiableContract.smodify.put({
myInternalMapping: { myInternalMapping: {
1234: 5678 1234: 5678
} }
...@@ -415,7 +413,7 @@ import { smoddit } from '@eth-optimism/smock' ...@@ -415,7 +413,7 @@ import { smoddit } from '@eth-optimism/smock'
const MyModifiableContractFactory = await smoddit('MyContract') const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...) const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({ await MyModifiableContract.smodify.put({
myInternalNestedMapping: { myInternalNestedMapping: {
1234: { 1234: {
4321: 5678 4321: 5678
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
"hardhat": "^2" "hardhat": "^2"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/core-utils": "0.1.10", "@eth-optimism/core-utils": "^0.1.10",
"@ethersproject/abi": "^5.0.13", "@ethersproject/abi": "^5.0.13",
"@ethersproject/abstract-provider": "^5.0.10", "@ethersproject/abstract-provider": "^5.0.10",
"bn.js": "^5.2.0" "bn.js": "^5.2.0"
......
...@@ -63,7 +63,7 @@ const smockifyFunction = ( ...@@ -63,7 +63,7 @@ const smockifyFunction = (
return return
}, },
get calls() { get calls() {
return vm._smockState.calls[contract.address.toLowerCase()] return (vm._smockState.calls[contract.address.toLowerCase()] || [])
.map((calldataBuf: Buffer) => { .map((calldataBuf: Buffer) => {
const sighash = toHexString(calldataBuf.slice(0, 4)) const sighash = toHexString(calldataBuf.slice(0, 4))
const fragment = contract.interface.getFunction(sighash) const fragment = contract.interface.getFunction(sighash)
......
...@@ -3,9 +3,8 @@ import hre from 'hardhat' ...@@ -3,9 +3,8 @@ import hre from 'hardhat'
import { fromHexString } from '@eth-optimism/core-utils' import { fromHexString } from '@eth-optimism/core-utils'
/* Internal Imports */ /* Internal Imports */
import { ModifiableContract, ModifiableContractFactory, Smodify } from './types' import { ModifiableContract, ModifiableContractFactory } from './types'
import { getStorageLayout, getStorageSlots } from './storage' import { getStorageLayout, getStorageSlots } from './storage'
import { bindSmod } from './binding'
import { toHexString32 } from '../utils' import { toHexString32 } from '../utils'
import { findBaseHardhatProvider } from '../common' import { findBaseHardhatProvider } from '../common'
...@@ -43,52 +42,48 @@ export const smoddit = async ( ...@@ -43,52 +42,48 @@ export const smoddit = async (
const contract: ModifiableContract = await originalDeployFn(...args) const contract: ModifiableContract = await originalDeployFn(...args)
contract._smodded = {} contract._smodded = {}
const put = (storage: any) => { const put = async (storage: any) => {
if (!storage) { if (!storage) {
return return
} }
const slots = getStorageSlots(layout, storage) const slots = getStorageSlots(layout, storage)
for (const slot of slots) { for (const slot of slots) {
contract._smodded[slot.hash.toLowerCase()] = slot.value await pStateManager.putContractStorage(
fromHexString(contract.address),
fromHexString(slot.hash.toLowerCase()),
fromHexString(slot.value)
)
} }
} }
const reset = () => {
contract._smodded = {}
}
const set = (storage: any) => {
contract.smodify.reset()
contract.smodify.put(storage)
}
const check = async (storage: any) => { const check = async (storage: any) => {
if (!storage) { if (!storage) {
return true return true
} }
const slots = getStorageSlots(layout, storage) const slots = getStorageSlots(layout, storage)
return slots.every(async (slot) => { for (const slot of slots) {
return ( if (
toHexString32( toHexString32(
await pStateManager.getContractStorage( await pStateManager.getContractStorage(
fromHexString(contract.address), fromHexString(contract.address),
fromHexString(slot.hash.toLowerCase()) fromHexString(slot.hash.toLowerCase())
) )
) === slot.value ) !== slot.value
) ) {
}) return false
}
}
return true
} }
contract.smodify = { contract.smodify = {
put, put,
reset,
set,
check, check,
} }
bindSmod(contract, provider)
return contract return contract
} }
......
...@@ -2,10 +2,8 @@ ...@@ -2,10 +2,8 @@
import { Contract, ContractFactory } from 'ethers' import { Contract, ContractFactory } from 'ethers'
export interface Smodify { export interface Smodify {
put: (storage: any) => void put: (storage: any) => Promise<void>
set: (storage: any) => void
check: (storage: any) => Promise<boolean> check: (storage: any) => Promise<boolean>
reset: () => void
} }
export interface Smodded { export interface Smodded {
......
...@@ -6,10 +6,23 @@ export const toHexString32 = ( ...@@ -6,10 +6,23 @@ export const toHexString32 = (
value: string | number | BigNumber | boolean value: string | number | BigNumber | boolean
): string => { ): string => {
if (typeof value === 'string' && value.startsWith('0x')) { if (typeof value === 'string' && value.startsWith('0x')) {
return '0x' + remove0x(value).padStart(64, '0').toLowerCase() // Known bug here is that bytes20 and address are indistinguishable but have to be treated
// differently. Address gets padded on the right, bytes20 gets padded on the left. Address is
// way more common so I'm going with the strategy of treating all bytes20 like addresses.
// Sorry to anyone who wants to smodify bytes20 values :-/ requires a bit of rewrite to fix.
if (value.length === 42) {
return '0x' + remove0x(value).padStart(64, '0').toLowerCase()
} else {
return '0x' + remove0x(value).padEnd(64, '0').toLowerCase()
}
} else if (typeof value === 'boolean') { } else if (typeof value === 'boolean') {
return toHexString32(value ? 1 : 0) return '0x' + `${value ? 1 : 0}`.padStart(64, '0')
} else { } else {
return toHexString32(BigNumber.from(value).toHexString()) return (
'0x' +
remove0x(BigNumber.from(value).toHexString())
.padStart(64, '0')
.toLowerCase()
)
} }
} }
...@@ -8,12 +8,20 @@ contract SimpleStorageGetter { ...@@ -8,12 +8,20 @@ contract SimpleStorageGetter {
bool valueB; bool valueB;
} }
address internal _address;
uint256 internal _constructorUint256; uint256 internal _constructorUint256;
uint256 internal _uint256; uint256 internal _uint256;
bool internal _bool; bool internal _bool;
SimpleStruct internal _SimpleStruct; SimpleStruct internal _SimpleStruct;
mapping (uint256 => uint256) _uint256Map; mapping (uint256 => uint256) _uint256Map;
mapping (uint256 => mapping (uint256 => uint256)) _uint256NestedMap; mapping (uint256 => mapping (uint256 => uint256)) _uint256NestedMap;
mapping (bytes5 => bool) _bytes5ToBoolMap;
mapping (address => bool) _addressToBoolMap;
mapping (address => address) _addressToAddressMap;
// Testing storage slot packing.
bool internal _packedA;
address internal _packedB;
constructor( constructor(
uint256 _inA uint256 _inA
...@@ -59,6 +67,16 @@ contract SimpleStorageGetter { ...@@ -59,6 +67,16 @@ contract SimpleStorageGetter {
return _bool; return _bool;
} }
function getAddress()
public
view
returns (
address _out
)
{
return _address;
}
function getSimpleStruct() function getSimpleStruct()
public public
view view
...@@ -93,4 +111,50 @@ contract SimpleStorageGetter { ...@@ -93,4 +111,50 @@ contract SimpleStorageGetter {
{ {
return _uint256NestedMap[_keyA][_keyB]; return _uint256NestedMap[_keyA][_keyB];
} }
function getBytes5ToBoolMapValue(
bytes5 _key
)
public
view
returns (
bool _out
)
{
return _bytes5ToBoolMap[_key];
}
function getAddressToBoolMapValue(
address _key
)
public
view
returns (
bool _out
)
{
return _addressToBoolMap[_key];
}
function getAddressToAddressMapValue(
address _key
)
public
view
returns (
address _out
)
{
return _addressToAddressMap[_key];
}
function getPackedAddress()
public
view
returns (
address
)
{
return _packedB;
}
} }
...@@ -26,7 +26,7 @@ describe('smoddit', () => { ...@@ -26,7 +26,7 @@ describe('smoddit', () => {
it('should be able to return a uint256', async () => { it('should be able to return a uint256', async () => {
const ret = 1234 const ret = 1234
smod.smodify.put({ await smod.smodify.put({
_uint256: ret, _uint256: ret,
}) })
...@@ -36,20 +36,41 @@ describe('smoddit', () => { ...@@ -36,20 +36,41 @@ describe('smoddit', () => {
it('should be able to return a boolean', async () => { it('should be able to return a boolean', async () => {
const ret = true const ret = true
smod.smodify.put({ await smod.smodify.put({
_bool: ret, _bool: ret,
}) })
expect(await smod.getBool()).to.equal(ret) expect(await smod.getBool()).to.equal(ret)
}) })
it('should be able to return an address', async () => {
const ret = '0x558ba9b8d78713fbf768c1f8a584485B4003f43F'
await smod.smodify.put({
_address: ret,
})
expect(await smod.getAddress()).to.equal(ret)
})
// TODO: Need to solve this with a rewrite.
it.skip('should be able to return an address in a packed storage slot', async () => {
const ret = '0x558ba9b8d78713fbf768c1f8a584485B4003f43F'
await smod.smodify.put({
_packedB: ret,
})
expect(await smod.getPackedAddress()).to.equal(ret)
})
it('should be able to return a simple struct', async () => { it('should be able to return a simple struct', async () => {
const ret = { const ret = {
valueA: BigNumber.from(1234), valueA: BigNumber.from(1234),
valueB: true, valueB: true,
} }
smod.smodify.put({ await smod.smodify.put({
_SimpleStruct: ret, _SimpleStruct: ret,
}) })
...@@ -62,7 +83,7 @@ describe('smoddit', () => { ...@@ -62,7 +83,7 @@ describe('smoddit', () => {
const retKey = 1234 const retKey = 1234
const retVal = 5678 const retVal = 5678
smod.smodify.put({ await smod.smodify.put({
_uint256Map: { _uint256Map: {
[retKey]: retVal, [retKey]: retVal,
}, },
...@@ -76,7 +97,7 @@ describe('smoddit', () => { ...@@ -76,7 +97,7 @@ describe('smoddit', () => {
const retKeyB = 4321 const retKeyB = 4321
const retVal = 5678 const retVal = 5678
smod.smodify.put({ await smod.smodify.put({
_uint256NestedMap: { _uint256NestedMap: {
[retKeyA]: { [retKeyA]: {
[retKeyB]: retVal, [retKeyB]: retVal,
...@@ -92,7 +113,7 @@ describe('smoddit', () => { ...@@ -92,7 +113,7 @@ describe('smoddit', () => {
it('should not return the set value if the value has been changed by the contract', async () => { it('should not return the set value if the value has been changed by the contract', async () => {
const ret = 1234 const ret = 1234
smod.smodify.put({ await smod.smodify.put({
_uint256: ret, _uint256: ret,
}) })
...@@ -104,12 +125,51 @@ describe('smoddit', () => { ...@@ -104,12 +125,51 @@ describe('smoddit', () => {
it('should return the set value if it was set in the constructor', async () => { it('should return the set value if it was set in the constructor', async () => {
const ret = 1234 const ret = 1234
smod.smodify.put({ await smod.smodify.put({
_constructorUint256: ret, _constructorUint256: ret,
}) })
expect(await smod.getConstructorUint256()).to.equal(1234) expect(await smod.getConstructorUint256()).to.equal(1234)
}) })
it('should be able to set values in a bytes5 => bool mapping', async () => {
const key = '0x0000005678'
const val = true
await smod.smodify.put({
_bytes5ToBoolMap: {
[key]: val,
},
})
expect(await smod.getBytes5ToBoolMapValue(key)).to.equal(val)
})
it('should be able to set values in a address => bool mapping', async () => {
const key = '0x558ba9b8d78713fbf768c1f8a584485B4003f43F'
const val = true
await smod.smodify.put({
_addressToBoolMap: {
[key]: val,
},
})
expect(await smod.getAddressToBoolMapValue(key)).to.equal(val)
})
it('should be able to set values in a address => address mapping', async () => {
const key = '0x558ba9b8d78713fbf768c1f8a584485B4003f43F'
const val = '0x063bE0Af9711a170BE4b07028b320C90705fec7C'
await smod.smodify.put({
_addressToAddressMap: {
[key]: val,
},
})
expect(await smod.getAddressToAddressMapValue(key)).to.equal(val)
})
}) })
}) })
}) })
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