Commit ea3785b1 authored by ben-chain's avatar ben-chain Committed by GitHub

first pass (#71)

parent a72141f4
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
contract Helper_GasMeasurer {
function measureCallGas(
address _target,
bytes memory _data
)
public
returns ( uint )
{
uint gasBefore;
uint gasAfter;
uint calldataStart;
uint calldataLength;
assembly {
calldataStart := add(_data,0x20)
calldataLength := mload(_data)
}
bool success;
assembly {
gasBefore := gas()
success := call(gas(), _target, 0, calldataStart, calldataLength, 0, 0)
gasAfter := gas()
}
require(success, "Call failed, but calls we want to measure gas for should succeed!");
return gasBefore - gasAfter;
}
}
......@@ -19,9 +19,9 @@
"build:typechain": "buidler typechain",
"test": "yarn run test:contracts",
"test:contracts": "buidler test --show-stack-traces",
"test:gas": "buidler test \"test/contracts/OVM/execution/OVM_StateManager.gas-spec.ts\" --no-compile --show-stack-traces",
"lint": "yarn run lint:typescript",
"lint:typescript": "tslint --format stylish --project .",
"lint:fix": "yarn run lint:fix:typescript",
"lint:fix:typescript": "prettier --config prettier-config.json --write \"buidler.config.ts\" \"{src,test}/**/*.ts\"",
"clean": "rm -rf ./artifacts ./build ./cache",
"deploy": "./bin/deploy.js"
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, ContractFactory, Signer, BigNumber } from 'ethers'
import _ from 'lodash'
/* Internal Imports */
import { DUMMY_ACCOUNTS, DUMMY_BYTES32, ZERO_ADDRESS, EMPTY_ACCOUNT_CODE_HASH, NON_ZERO_ADDRESS, NON_NULL_BYTES32, STORAGE_XOR_VALUE } from '../../../helpers'
const DUMMY_ACCOUNT = DUMMY_ACCOUNTS[0]
const DUMMY_KEY = DUMMY_BYTES32[0]
const DUMMY_VALUE_1 = DUMMY_BYTES32[1]
const DUMMY_VALUE_2 = DUMMY_BYTES32[2]
describe('OVM_StateManager gas consumption', () => {
let owner: Signer
before(async () => {
;[owner] = await ethers.getSigners()
})
let Factory__OVM_StateManager: ContractFactory
let Helper_GasMeasurer: Contract
before(async () => {
Factory__OVM_StateManager = await ethers.getContractFactory(
'OVM_StateManager'
)
Helper_GasMeasurer = await (await (await ethers.getContractFactory(
'Helper_GasMeasurer'
)).deploy()).connect(owner)
})
let OVM_StateManager: Contract
beforeEach(async () => {
OVM_StateManager = (
await Factory__OVM_StateManager.deploy(await owner.getAddress())
).connect(owner)
await OVM_StateManager.setExecutionManager(Helper_GasMeasurer.address)
})
const measure = (
methodName: string,
methodArgs: Array<any> = [],
doFirst: () => Promise<any> = async () => {return}
) => {
it('measured consumption!', async () => {
await doFirst()
await getSMGasCost(methodName, methodArgs)
})
}
const getSMGasCost = async (methodName: string, methodArgs: Array<any> = []): Promise<number> => {
const gasCost: number = await Helper_GasMeasurer.callStatic.measureCallGas(
OVM_StateManager.address,
OVM_StateManager.interface.encodeFunctionData(
methodName,
methodArgs
)
)
console.log(` calculated gas cost of ${gasCost}`)
return gasCost
}
const setupFreshAccount = async () => {
await OVM_StateManager.putAccount(DUMMY_ACCOUNT.address, {
...DUMMY_ACCOUNT.data,
isFresh: true,
})
}
const setupNonFreshAccount = async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
DUMMY_ACCOUNT.data
)
}
const putSlot = async (value: string) => {
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNT.address,
DUMMY_KEY,
value
)
}
describe('ItemState testAndSetters', () => {
describe('testAndSetAccountLoaded', () => {
describe('when account ItemState is ITEM_UNTOUCHED', () => {
measure(
'testAndSetAccountLoaded',
[NON_ZERO_ADDRESS]
)
})
describe('when account ItemState is ITEM_LOADED', () => {
measure(
'testAndSetAccountLoaded',
[NON_ZERO_ADDRESS],
async () => {
await OVM_StateManager.testAndSetAccountLoaded(NON_ZERO_ADDRESS)
}
)
})
describe('when account ItemState is ITEM_CHANGED', () => {
measure(
'testAndSetAccountLoaded',
[NON_ZERO_ADDRESS],
async () => {
await OVM_StateManager.testAndSetAccountChanged(NON_ZERO_ADDRESS)
}
)
})
})
describe('testAndSetAccountChanged', () => {
describe('when account ItemState is ITEM_UNTOUCHED', () => {
measure(
'testAndSetAccountChanged',
[NON_ZERO_ADDRESS]
)
})
describe('when account ItemState is ITEM_LOADED', () => {
measure(
'testAndSetAccountChanged',
[NON_ZERO_ADDRESS],
async () => {
await OVM_StateManager.testAndSetAccountLoaded(NON_ZERO_ADDRESS)
}
)
})
describe('when account ItemState is ITEM_CHANGED', () => {
measure(
'testAndSetAccountChanged',
[NON_ZERO_ADDRESS],
async () => {
await OVM_StateManager.testAndSetAccountChanged(NON_ZERO_ADDRESS)
}
)
})
})
describe('testAndSetContractStorageLoaded', () => {
describe('when storage ItemState is ITEM_UNTOUCHED', () => {
measure(
'testAndSetContractStorageLoaded',
[NON_ZERO_ADDRESS, DUMMY_KEY]
)
})
describe('when storage ItemState is ITEM_LOADED', () => {
measure(
'testAndSetContractStorageLoaded',
[NON_ZERO_ADDRESS, DUMMY_KEY],
async () => {
await OVM_StateManager.testAndSetContractStorageLoaded(NON_ZERO_ADDRESS, DUMMY_KEY)
}
)
})
describe('when storage ItemState is ITEM_CHANGED', () => {
measure(
'testAndSetContractStorageLoaded',
[NON_ZERO_ADDRESS, DUMMY_KEY],
async () => {
await OVM_StateManager.testAndSetContractStorageChanged(NON_ZERO_ADDRESS, DUMMY_KEY)
}
)
})
})
describe('testAndSetContractStorageChanged', () => {
describe('when storage ItemState is ITEM_UNTOUCHED', () => {
measure(
'testAndSetContractStorageChanged',
[NON_ZERO_ADDRESS, DUMMY_KEY]
)
})
describe('when storage ItemState is ITEM_LOADED', () => {
measure(
'testAndSetContractStorageChanged',
[NON_ZERO_ADDRESS, DUMMY_KEY],
async () => {
await OVM_StateManager.testAndSetContractStorageLoaded(NON_ZERO_ADDRESS, DUMMY_KEY)
}
)
})
describe('when storage ItemState is ITEM_CHANGED', () => {
measure(
'testAndSetContractStorageChanged',
[NON_ZERO_ADDRESS, DUMMY_KEY],
async () => {
await OVM_StateManager.testAndSetContractStorageChanged(NON_ZERO_ADDRESS, DUMMY_KEY)
}
)
})
})
})
describe('incrementTotalUncommittedAccounts', () => {
describe('when totalUncommittedAccounts is 0', () => {
measure('incrementTotalUncommittedAccounts')
})
describe('when totalUncommittedAccounts is nonzero', () => {
const doFirst = async () => {
await OVM_StateManager.incrementTotalUncommittedAccounts()
}
measure('incrementTotalUncommittedAccounts', [], doFirst)
})
})
describe('incrementTotalUncommittedContractStorage', () => {
describe('when totalUncommittedContractStorage is 0', () => {
measure('incrementTotalUncommittedContractStorage')
})
describe('when totalUncommittedContractStorage is nonzero', () => {
const doFirst = async () => {
await OVM_StateManager.incrementTotalUncommittedContractStorage()
}
measure('incrementTotalUncommittedContractStorage', [], doFirst)
})
})
describe('hasAccount', () => {
describe('when it does have the account', () => {
const doFirst = async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
DUMMY_ACCOUNT.data
)
}
measure(
'hasAccount',
[DUMMY_ACCOUNT.address],
doFirst
)
})
})
describe('hasEmptyAccount', () => {
describe('when it does have an empty account', () => {
measure(
'hasEmptyAccount',
[DUMMY_ACCOUNT.address],
async () => {
await OVM_StateManager.putAccount(DUMMY_ACCOUNT.address, {
...DUMMY_ACCOUNT.data,
codeHash: EMPTY_ACCOUNT_CODE_HASH,
})
}
)
})
describe('when it has an account which is not emtpy', () => {
measure(
'hasEmptyAccount',
[DUMMY_ACCOUNT.address],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
DUMMY_ACCOUNT.data
)
}
)
})
})
describe('setAccountNonce', () => {
describe('when the nonce is 0 and set to 0', () => {
measure(
'setAccountNonce',
[DUMMY_ACCOUNT.address, 0],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
{
...DUMMY_ACCOUNT.data,
nonce: 0
}
)
}
)
})
describe('when the nonce is 0 and set to nonzero', () => {
measure(
'setAccountNonce',
[DUMMY_ACCOUNT.address, 1],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
{
...DUMMY_ACCOUNT.data,
nonce: 0
}
)
}
)
})
describe('when the nonce is nonzero and set to 0', () => {
measure(
'setAccountNonce',
[DUMMY_ACCOUNT.address, 0],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
{
...DUMMY_ACCOUNT.data,
nonce: 1
}
)
}
)
})
describe('when the nonce is nonzero and set to nonzero', () => {
measure(
'setAccountNonce',
[DUMMY_ACCOUNT.address, 2],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
{
...DUMMY_ACCOUNT.data,
nonce: 1
}
)
}
)
})
})
describe('getAccountNonce', () => {
describe('when the nonce is 0', () => {
measure(
'getAccountNonce',
[DUMMY_ACCOUNT.address]
)
})
describe('when the nonce is nonzero', () => {
measure(
'getAccountNonce',
[DUMMY_ACCOUNT.address],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
{
...DUMMY_ACCOUNT.data,
nonce: 1
}
)
}
)
})
})
describe('getAccountEthAddress', () => {
describe('when the ethAddress is a random address', () => {
measure(
'getAccountEthAddress',
[DUMMY_ACCOUNT.address],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
{
...DUMMY_ACCOUNT.data,
ethAddress: NON_ZERO_ADDRESS
}
)
}
)
})
describe('when the ethAddress is zero', () => {
measure(
'getAccountEthAddress',
[DUMMY_ACCOUNT.address],
async () => {
await OVM_StateManager.putAccount(
DUMMY_ACCOUNT.address,
{
...DUMMY_ACCOUNT.data,
ethAddress: ZERO_ADDRESS
}
)
}
)
})
})
describe('initPendingAccount', () => {
// note: this method should only be accessibl if _hasEmptyAccount is true, so it should always be empty nonce etc
measure(
'initPendingAccount',
[NON_ZERO_ADDRESS]
)
})
describe('commitPendingAccount', () => {
// this should only set ethAddress and codeHash from ZERO to NONZERO, so one case should be sufficient
measure(
'commitPendingAccount',
[
NON_ZERO_ADDRESS,
NON_ZERO_ADDRESS,
NON_NULL_BYTES32
],
async () => {
await OVM_StateManager.initPendingAccount(NON_ZERO_ADDRESS)
}
)
})
describe('getContractStorage', () => {
// confirm with kelvin that this covers all cases
describe('when the account isFresh', () => {
describe('when the storage slot value has not been set', () => {
measure(
'getContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
setupFreshAccount
)
})
describe('when the storage slot has already been set', () => {
describe('when the storage slot value is STORAGE_XOR_VALUE', () => {
measure(
'getContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
async () => {
await setupFreshAccount()
await putSlot(STORAGE_XOR_VALUE)
}
)
})
describe('when the storage slot value is something other than STORAGE_XOR_VALUE', () => {
measure(
'getContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
async () => {
await setupFreshAccount()
await putSlot(DUMMY_VALUE_1)
}
)
})
})
})
describe('when the account is not fresh', () => {
describe('when the storage slot value is STORAGE_XOR_VALUE', () => {
measure(
'getContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
async () => {
await setupNonFreshAccount()
await putSlot(STORAGE_XOR_VALUE)
}
)
})
describe('when the storage slot value is something other than STORAGE_XOR_VALUE', () => {
measure(
'getContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
async () => {
await setupNonFreshAccount()
await putSlot(DUMMY_VALUE_1)
}
)
})
})
})
describe('putContractStorage', () => {
const relevantValues = [
DUMMY_VALUE_1,
DUMMY_VALUE_2,
STORAGE_XOR_VALUE
]
for (let preValue of relevantValues) {
for (let postValue of relevantValues) {
describe(`when overwriting ${preValue} with ${postValue}`, () => {
measure(
'putContractStorage',
[NON_ZERO_ADDRESS, DUMMY_KEY, postValue],
async () => {
await OVM_StateManager.putContractStorage(
NON_ZERO_ADDRESS,
DUMMY_KEY,
preValue
)
}
)
})
}
}
})
describe('hasContractStorage', () => {
describe('when the account is fresh', () => {
describe('when the storage slot has not been set', () => {
measure(
'hasContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
setupFreshAccount
)
})
describe('when the slot has already been set', () => {
measure(
'hasContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
async () => {
await setupFreshAccount()
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNT.address,
DUMMY_KEY,
DUMMY_VALUE_1
)
}
)
})
})
describe('when the account is not fresh', () => {
measure(
'hasContractStorage',
[DUMMY_ACCOUNT.address, DUMMY_KEY],
async () => {
await OVM_StateManager.putContractStorage(
DUMMY_ACCOUNT.address,
DUMMY_KEY,
DUMMY_VALUE_1
)
}
)
})
})
})
\ No newline at end of file
......@@ -6,12 +6,9 @@ import { Contract, ContractFactory, Signer, BigNumber } from 'ethers'
import _ from 'lodash'
/* Internal Imports */
import { DUMMY_ACCOUNTS, DUMMY_BYTES32, ZERO_ADDRESS } from '../../../helpers'
import { DUMMY_ACCOUNTS, DUMMY_BYTES32, ZERO_ADDRESS, EMPTY_ACCOUNT_CODE_HASH, KECCAK_256_NULL } from '../../../helpers'
const EMPTY_ACCOUNT_CODE_HASH =
'0x00004B1DC0DE000000004B1DC0DE000000004B1DC0DE000000004B1DC0DE0000'
const KECCAK_256_NULL =
'0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
describe('OVM_StateManager', () => {
let signer1: Signer
......
......@@ -26,6 +26,9 @@ export const NON_ZERO_ADDRESS = makeAddress('11')
export const VERIFIED_EMPTY_CONTRACT_HASH =
'0x00004B1DC0DE000000004B1DC0DE000000004B1DC0DE000000004B1DC0DE0000'
export const STORAGE_XOR_VALUE =
'0xFEEDFACECAFEBEEFFEEDFACECAFEBEEFFEEDFACECAFEBEEFFEEDFACECAFEBEEF'
export const NUISANCE_GAS_COSTS = {
NUISANCE_GAS_SLOAD: 20000,
NUISANCE_GAS_SSTORE: 20000,
......@@ -42,3 +45,8 @@ export const STORAGE_XOR =
export const getStorageXOR = (key: string): string => {
return toHexString(xor(fromHexString(key), fromHexString(STORAGE_XOR)))
}
export const EMPTY_ACCOUNT_CODE_HASH =
'0x00004B1DC0DE000000004B1DC0DE000000004B1DC0DE000000004B1DC0DE0000'
export const KECCAK_256_NULL =
'0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
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