Skip to content

Commit

Permalink
core-utils: add GenesisJsonProvider and fix tests
Browse files Browse the repository at this point in the history
The `GenesisJsonProvider` implements the `ethers.Provider`
interface and is constructed with a geth genesis file, either
as an object or as a file to be read from disk. It implements
a subset of the RPC methods that use the genesis file
as the backing storage. It includes tests for its correctness.
Not all methods are implemented, just the ones for the regenesis
testing.

This PR also moves the tests around in the `core-utils` package
as some of the tests were being skipped. The `tests` directory is
flattened, having so many subdirectories was not needed. The
`package.json` test script is updated to ensure that all tests
are run.

Also add some deps that are required for the `GenesisJsonProvider`.
  • Loading branch information
tynes committed Nov 2, 2021
1 parent cafbbe4 commit 75d64cc
Show file tree
Hide file tree
Showing 14 changed files with 440 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/funny-kings-swim.md
@@ -0,0 +1,5 @@
---
'@eth-optimism/core-utils': patch
---

Add GenesisJsonProvider
8 changes: 6 additions & 2 deletions packages/core-utils/package.json
Expand Up @@ -17,8 +17,8 @@
"lint:check": "eslint .",
"lint:fix": "yarn lint:check --fix",
"pre-commit": "lint-staged",
"test": "ts-mocha test/**/*.spec.ts",
"test:coverage": "nyc ts-mocha test/**/*.spec.ts && nyc merge .nyc_output coverage.json"
"test": "ts-mocha test/*.spec.ts",
"test:coverage": "nyc ts-mocha test/*.spec.ts && nyc merge .nyc_output coverage.json"
},
"devDependencies": {
"@types/chai": "^4.2.18",
Expand Down Expand Up @@ -46,8 +46,12 @@
},
"dependencies": {
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/bytes": "^5.5.0",
"@ethersproject/properties": "^5.5.0",
"@ethersproject/providers": "^5.4.5",
"chai": "^4.3.4",
"ethereumjs-util": "^7.1.3",
"ethers": "^5.4.5",
"lodash": "^4.17.21"
}
Expand Down
5 changes: 5 additions & 0 deletions packages/core-utils/src/common/hex-strings.ts
@@ -1,5 +1,6 @@
/* Imports: External */
import { BigNumber, ethers } from 'ethers'
import { hexZeroPad } from '@ethersproject/bytes'

/**
* Removes "0x" from start of a string if it exists.
Expand Down Expand Up @@ -92,3 +93,7 @@ export const hexStringEquals = (stringA: string, stringB: string): boolean => {

return stringA.toLowerCase() === stringB.toLowerCase()
}

export const bytes32ify = (value: number | BigNumber): string => {
return hexZeroPad(BigNumber.from(value).toHexString(), 32)
}
2 changes: 1 addition & 1 deletion packages/core-utils/src/common/test-utils.ts
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { BigNumber } from 'ethers'
import { sleep } from './misc'
import { sleep } from './misc'

interface deviationRanges {
percentUpperDeviation?: number
Expand Down
1 change: 1 addition & 0 deletions packages/core-utils/src/index.ts
Expand Up @@ -8,3 +8,4 @@ export * from './bcfg'
export * from './fees'
export * from './provider'
export * from './alias'
export * from './types'
216 changes: 216 additions & 0 deletions packages/core-utils/src/provider.ts
Expand Up @@ -3,7 +3,26 @@
*/

import { ethers } from 'ethers'
import { BigNumber, BigNumberish } from '@ethersproject/bignumber'
import { Deferrable } from '@ethersproject/properties'
import { Provider } from '@ethersproject/providers'
import {
Provider as AbstractProvider,
EventType,
TransactionRequest,
TransactionResponse,
TransactionReceipt,
Filter,
Log,
Block,
BlockWithTransactions,
BlockTag,
Listener,
} from '@ethersproject/abstract-provider'
import { KECCAK256_RLP_S, KECCAK256_NULL_S } from 'ethereumjs-util'

import { State, Genesis } from './types'
import { bytes32ify, remove0x, add0x } from './common/hex-strings'

// Copied from @ethersproject/providers since it is not
// currently exported
Expand Down Expand Up @@ -38,3 +57,200 @@ export const FallbackProvider = (config: string | FallbackProviderConfig[]) => {
}
return new ethers.providers.FallbackProvider(config)
}

export class GenesisJsonProvider implements AbstractProvider {
genesis: Genesis

constructor(genesis: string | Genesis) {
if (typeof genesis === 'string') {
this.genesis = require(genesis)
} else if (typeof genesis === 'object') {
this.genesis = genesis
}

if (this.genesis === null) {
throw new Error('Must initialize with genesis object')
}
}

async getBalance(
addressOrName: string,
blockTag?: BlockTag
): Promise<BigNumber> {
const address = remove0x(addressOrName)
const account = this.genesis.alloc[address]
if (!account) {
return BigNumber.from(0)
}
return BigNumber.from(account.balance)
}

async getTransactionCount(
addressOrName: string,
blockTag?: BlockTag
): Promise<number> {
const address = remove0x(addressOrName)
const account = this.genesis.alloc[address]
if (!account) {
return 0
}
return account.nonce
}

async getCode(addressOrName: string, blockTag?: BlockTag): Promise<string> {
const address = remove0x(addressOrName)
const account = this.genesis.alloc[address]
if (!account) {
return '0x'
}
return add0x(account.code)
}

async getStorageAt(
addressOrName: string,
position: BigNumber | number,
blockTag?: BlockTag
): Promise<string> {
const address = remove0x(addressOrName)
const account = this.genesis.alloc[address]
if (!account) {
return '0x'
}
const bytes32 = bytes32ify(position)
const storage = account.storage[remove0x(bytes32)]
if (!storage) {
return '0x'
}
return add0x(storage)
}

async call(
transaction: Deferrable<TransactionRequest>,
blockTag?: BlockTag | Promise<BlockTag>
): Promise<string> {
throw new Error('Unsupported Method: call')
}

async send(method: string, args: Array<any>): Promise<any> {
switch (method) {
case 'eth_getProof': {
const address = args[0]
if (!address) {
throw new Error('Must pass address as first arg')
}
const account = this.genesis.alloc[remove0x(address)]
// The account doesn't exist or is an EOA
if (!account || !account.code || account.code === '0x') {
return {
codeHash: add0x(KECCAK256_NULL_S),
storageHash: add0x(KECCAK256_RLP_S),
}
}
return {
codeHash: ethers.utils.keccak256('0x' + account.code),
storageHash: add0x(account.root),
}
}

default:
throw new Error(`Unsupported Method: send ${method}`)
}
}

async getNetwork() {
return undefined
}

async getBlockNumber(): Promise<number> {
return 0
}
async getGasPrice(): Promise<BigNumber> {
return BigNumber.from(0)
}

async getFeeData() {
return undefined
}

async sendTransaction(
signedTransaction: string | Promise<string>
): Promise<TransactionResponse> {
throw new Error('Unsupported Method: sendTransaction')
}

async estimateGas(
transaction: Deferrable<TransactionRequest>
): Promise<BigNumber> {
return BigNumber.from(0)
}

async getBlock(
blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>
): Promise<Block> {
throw new Error('Unsupported Method: getBlock')
}
async getBlockWithTransactions(
blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>
): Promise<BlockWithTransactions> {
throw new Error('Unsupported Method: getBlockWithTransactions')
}
async getTransaction(transactionHash: string): Promise<TransactionResponse> {
throw new Error('Unsupported Method: getTransaction')
}
async getTransactionReceipt(
transactionHash: string
): Promise<TransactionReceipt> {
throw new Error('Unsupported Method: getTransactionReceipt')
}

async getLogs(filter: Filter): Promise<Array<Log>> {
throw new Error('Unsupported Method: getLogs')
}

async resolveName(name: string | Promise<string>): Promise<null | string> {
throw new Error('Unsupported Method: resolveName')
}
async lookupAddress(
address: string | Promise<string>
): Promise<null | string> {
throw new Error('Unsupported Method: lookupAddress')
}

on(eventName: EventType, listener: Listener): Provider {
throw new Error('Unsupported Method: on')
}
once(eventName: EventType, listener: Listener): Provider {
throw new Error('Unsupported Method: once')
}
emit(eventName: EventType, ...args: Array<any>): boolean {
throw new Error('Unsupported Method: emit')
}
listenerCount(eventName?: EventType): number {
throw new Error('Unsupported Method: listenerCount')
}
listeners(eventName?: EventType): Array<Listener> {
throw new Error('Unsupported Method: listeners')
}
off(eventName: EventType, listener?: Listener): Provider {
throw new Error('Unsupported Method: off')
}
removeAllListeners(eventName?: EventType): Provider {
throw new Error('Unsupported Method: removeAllListeners')
}
addListener(eventName: EventType, listener: Listener): Provider {
throw new Error('Unsupported Method: addListener')
}
removeListener(eventName: EventType, listener: Listener): Provider {
throw new Error('Unsupported Method: removeListener')
}

async waitForTransaction(
transactionHash: string,
confirmations?: number,
timeout?: number
): Promise<TransactionReceipt> {
throw new Error('Unsupported Method: waitForTransaction')
}

readonly _isProvider: boolean
}
39 changes: 39 additions & 0 deletions packages/core-utils/src/types.ts
@@ -0,0 +1,39 @@
// Optimism PBC 2021

// Represents the ethereum state
export interface State {
[address: string]: {
nonce: number
balance: string
codeHash: string
root: string
code?: string
storage?: {
[key: string]: string
}
}
}

// Represents a genesis file that geth can consume
export interface Genesis {
config: {
chainId: number
homesteadBlock: number
eip150Block: number
eip155Block: number
eip158Block: number
byzantiumBlock: number
constantinopleBlock: number
petersburgBlock: number
istanbulBlock: number
muirGlacierBlock: number
clique: {
period: number
epoch: number
}
}
difficulty: string
gasLimit: string
extraData: string
alloc: State
}
@@ -1,11 +1,11 @@
import '../setup'
import './setup'

/* Internal Imports */
import {
encodeAppendSequencerBatch,
decodeAppendSequencerBatch,
sequencerBatch,
} from '../../src'
} from '../src'
import { expect } from 'chai'

describe('BatchEncoder', () => {
Expand Down Expand Up @@ -43,7 +43,7 @@ describe('BatchEncoder', () => {

it('should work with mainnet calldata', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const data = require('../fixtures/appendSequencerBatch.json')
const data = require('./fixtures/appendSequencerBatch.json')
for (const calldata of data.calldata) {
const decoded = sequencerBatch.decode(calldata)
const encoded = sequencerBatch.encode(decoded)
Expand Down
@@ -1,5 +1,5 @@
import { expect } from '../setup'
import * as fees from '../../src/fees'
import { expect } from './setup'
import * as fees from '../src/fees'
import { BigNumber, utils } from 'ethers'

const hundredBillion = 10 ** 11
Expand Down
@@ -1,4 +1,4 @@
import { expect } from '../setup'
import { expect } from './setup'
import { BigNumber } from 'ethers'

/* Imports: Internal */
Expand All @@ -9,7 +9,7 @@ import {
fromHexString,
toHexString,
padHexString,
} from '../../src'
} from '../src'

describe('remove0x', () => {
it('should return undefined', () => {
Expand Down
@@ -1,7 +1,7 @@
import { expect } from '../setup'
import { expect } from './setup'

/* Imports: Internal */
import { sleep } from '../../src'
import { sleep } from '../src'

describe('sleep', async () => {
it('should return wait input amount of ms', async () => {
Expand Down

0 comments on commit 75d64cc

Please sign in to comment.