Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7747bab
provider: initial commit
tynes Aug 26, 2020
75ecb61
wip: pre-refactor
tynes Aug 28, 2020
897be09
signer: basic tests passing
tynes Aug 28, 2020
ddba6f0
provider: export from package
tynes Aug 28, 2020
8a80b82
test: initial tests
tynes Aug 28, 2020
9924e42
provider: add deps + debug script
tynes Aug 28, 2020
3de89ee
provider: refactoring + tests
tynes Sep 3, 2020
ca9ee37
provider: cleanup
tynes Sep 3, 2020
5c6084a
sighash: refactor
tynes Sep 3, 2020
afdeb52
test: use ganache instead of external geth
tynes Sep 3, 2020
d394a67
sighash: new sighash
tynes Sep 3, 2020
d038195
lint: auto-fix
tynes Sep 4, 2020
9ea344d
types: delete
tynes Sep 4, 2020
aca34c7
provider: clean up
tynes Sep 4, 2020
1f92f4f
sighash: commit to chainid
tynes Sep 4, 2020
6f8ff09
provider: bugfix, don't use prefix
tynes Sep 4, 2020
5035432
sighash: ethsign bugfixes
tynes Sep 8, 2020
c98b705
deps: remove bufio
tynes Sep 8, 2020
cde5042
deps: remove redundant
tynes Sep 8, 2020
50f446a
signer: remove unnecessary dep
tynes Sep 8, 2020
909a70b
deps: update yarn.lock
tynes Sep 8, 2020
a8ad3ce
lint: cleanup
tynes Sep 9, 2020
534de3c
sighash: move chainid
tynes Sep 9, 2020
4965cc7
license: fix and cleanup
tynes Sep 10, 2020
4cbf1fb
yarn.lock: regenerate
tynes Sep 10, 2020
97b106b
test: skip e2e tests
tynes Sep 10, 2020
02894ce
pkg: don't exit early on tests
tynes Sep 10, 2020
ca51a76
lerna: add optimism-provider package
tynes Sep 10, 2020
0cbb144
test/data: remove mock contract
tynes Sep 10, 2020
2a23f02
readme: update
tynes Sep 10, 2020
a1c93c6
package: rename to provider
tynes Sep 10, 2020
53b6c22
pkg: name, author
tynes Sep 11, 2020
7180e2a
lint: fix
tynes Sep 11, 2020
4229e49
deps: commit new yarn.lock
tynes Sep 11, 2020
c5d4ae7
test: fix
tynes Sep 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"packages/core-db/*",
"packages/core-utils/*",
"packages/ovm-toolchain/*",
"packages/provider/*",
"packages/rollup-core/*",
"packages/rollup-services/*"
],
Expand Down
20 changes: 20 additions & 0 deletions packages/provider/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
This software is licensed under the MIT License.

Copyright 2020 Optimism PBC

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 changes: 20 additions & 0 deletions packages/provider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Optimism Provider

The `OptimismProvider` extends the ethers.js `JsonRpcProvider` and
implements all of the same methods. It will submit transactions
to the Optimism Sequencer and needs a `Web3Provider` based provider
to manage keys for any transaction signing.

## Usage

```js
import { OptimismProvider } from '@eth-optimism/provider'
import { Web3Provider } from '@ethersproject/providers'

// Uses a Web3Provider to manage keys, pass in `window.ethereum` or
// another key management backend.
const web3 = new Web3Provider()

// Accepts either a URL or a network name (main, kovan)
const provider = new OptimismProvider('http://localhost:8545', web3)
```
7 changes: 7 additions & 0 deletions packages/provider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Copyright 2020, Optimism PBC
* MIT License
* https://github.com/ethereum-optimism
*/

export * from './src/app'
42 changes: 42 additions & 0 deletions packages/provider/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@eth-optimism/provider",
"version": "0.0.0",
"description": "Web3 provider for Optimism",
"main": "index.js",
"scripts": {
"all": "yarn clean && yarn build && yarn test && yarn fix && yarn lint",
"build": "tsc -p .",
"clean": "rimraf build/",
"fix": "prettier --config ../../prettier-config.json --write 'index.ts' '{src,test}/**/*.ts'",
"lint": "tslint --format stylish --project .",
"test": "mocha --require ts-node/register 'test/**/*.spec.ts' --timeout 5000",
"test:debug": "mocha debug --require ts-node/register 'test/**/*.spec.ts' --timeout 5000"
},
"keywords": [],
"author": "Optimism",
"license": "MIT",
"dependencies": {
"@eth-optimism/core-utils": "0.0.1-alpha.27",
"@ethersproject/abstract-provider": "^5.0.3",
"@ethersproject/abstract-signer": "^5.0.3",
"@ethersproject/bignumber": "^5.0.6",
"@ethersproject/bytes": "^5.0.4",
"@ethersproject/keccak256": "^5.0.3",
"@ethersproject/logger": "^5.0.5",
"@ethersproject/networks": "^5.0.3",
"@ethersproject/properties": "^5.0.3",
"@ethersproject/providers": "^5.0.7",
"@ethersproject/transactions": "^5.0.4",
"@ethersproject/wallet": "^5.0.3",
"@ethersproject/web": "^5.0.5",
"bn.js": "^5.1.3"
},
"devDependencies": {
"@eth-optimism/ovm-toolchain": "0.0.1-alpha.1",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"mocha": "^8.1.2",
"ts-node": "^8.2.0",
"typescript": "^3.5.1"
}
}
9 changes: 9 additions & 0 deletions packages/provider/src/app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Copyright 2020, Optimism PBC
* MIT License
* https://github.com/ethereum-optimism
*/

export * from './provider'
export * from './signer'
export * from './utils'
161 changes: 161 additions & 0 deletions packages/provider/src/app/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* Copyright 2020, Optimism PBC
* MIT License
* https://github.com/ethereum-optimism
*
* This code is based on ethers.js
* Copyright (c) 2019 Richard Moore
* MIT License
* https://github.com/ethers-io/ethers.js
*/

/**
* This file is copied from ethers and implements a custom `network` list.
* As Optimism is available on more networks, add additional entries to
* the `network` list below.
*/

import { Network, Networkish } from '@ethersproject/networks'
import { Logger } from '@ethersproject/logger'
import { ConnectionInfo } from '@ethersproject/web'
import { isUrl } from './utils'

import pkg = require('../../package.json')

const version = pkg.version
const logger = new Logger(version)

type DefaultProviderFunc = (providers: any, options?: any) => any

interface Renetworkable extends DefaultProviderFunc {
renetwork: (network: Network) => DefaultProviderFunc
}

function isRenetworkable(value: any): value is Renetworkable {
return value && typeof value.renetwork === 'function'
}

export const homestead: Network = {
chainId: 1,
ensAddress: null,
name: 'homestead',
_defaultProvider: null,
}

// TODO(mark): add each supported Network to this list
const networks = [homestead]

/**
* getNetwork
*
* Converts a named common networks or chain ID (network ID) to a Network
* and verifies a network is a valid Network..
*/
export function getNetwork(network: Networkish): Network {
// No network (null)
if (network == null) {
return null
}

if (typeof network === 'number') {
for (const name of Object.keys(networks)) {
// tslint:disable-next-line:no-shadowed-variable
const standard = networks[name]
if (standard.chainId === network) {
return {
name: standard.name,
chainId: standard.chainId,
ensAddress: standard.ensAddress || null,
_defaultProvider: standard._defaultProvider || null,
}
}
}

return {
chainId: network,
name: 'unknown',
}
}

if (typeof network === 'string') {
// tslint:disable-next-line:no-shadowed-variable
const standard = networks[network]
if (standard == null) {
return null
}
return {
name: standard.name,
chainId: standard.chainId,
ensAddress: standard.ensAddress,
_defaultProvider: standard._defaultProvider || null,
}
}

const standard = networks[network.name]

// Not a standard network; check that it is a valid network in general
if (!standard) {
if (typeof network.chainId !== 'number') {
logger.throwArgumentError('invalid network chainId', 'network', network)
}
return network
}

// Make sure the chainId matches the expected network chainId (or is 0; disable EIP-155)
if (network.chainId !== 0 && network.chainId !== standard.chainId) {
logger.throwArgumentError('network chainId mismatch', 'network', network)
}

// @TODO: In the next major version add an attach function to a defaultProvider
// class and move the _defaultProvider internal to this file (extend Network)
let defaultProvider: DefaultProviderFunc = network._defaultProvider || null
if (defaultProvider == null && standard._defaultProvider) {
// tslint:disable-next-line:prefer-conditional-expression
if (isRenetworkable(standard._defaultProvider)) {
defaultProvider = standard._defaultProvider.renetwork(network)
} else {
defaultProvider = standard._defaultProvider
}
}

// Standard Network (allow overriding the ENS address)
return {
name: network.name,
chainId: standard.chainId,
ensAddress: network.ensAddress || standard.ensAddress || null,
_defaultProvider: defaultProvider,
}
}

// Based on the newtork, return the public URL of the optimism nodes
// TODO(mark): add public urls here
export function getUrl(
network: Network,
extra: Networkish
): string | ConnectionInfo {
let host: string = null

// Allow for custom urls to be passed in
if (typeof extra === 'string' && isUrl(extra)) {
return { url: extra }
}

// List of publically available urls to use
// TODO(mark): in this case, turn off calls for `eth_getChainId`
switch (network ? network.name : 'unknown') {
case 'main':
host = '' // TODO: once the url of mainnet is known
break
default:
logger.throwError('unsupported network', Logger.errors.INVALID_ARGUMENT, {
argument: 'network',
value: network,
})
}

const connection: ConnectionInfo = {
url: `http://${host}`,
}

return connection
}
105 changes: 105 additions & 0 deletions packages/provider/src/app/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Copyright 2020, Optimism PBC
* MIT License
* https://github.com/ethereum-optimism
*/

import { Logger } from '@ethersproject/logger'
import { Network, Networkish } from '@ethersproject/networks'
import {
UrlJsonRpcProvider,
JsonRpcSigner,
JsonRpcProvider,
Web3Provider,
} from '@ethersproject/providers'
import { defineReadOnly, getStatic } from '@ethersproject/properties'
import { ConnectionInfo } from '@ethersproject/web'
import { verifyMessage } from '@ethersproject/wallet'
import { Provider } from '@ethersproject/abstract-provider'
import { joinSignature, SignatureLike } from '@ethersproject/bytes'
import { OptimismSigner } from './signer'
import * as utils from './utils'
import { getNetwork, getUrl } from './network'

import pkg = require('../../package.json')
const version = pkg.version
const logger = new Logger(version)

/**
* The OptimismProvider is an ethers.js JsonRpcProvider that
* utilizes a new signature hashing scheme meant for usage with
* the Optimism node. Transactions that are signed with this scheme
* are sent to a new endpoint `eth_sendRawEthSignTransaction`.
*/

export class OptimismProvider extends JsonRpcProvider {
private readonly _ethereum: Web3Provider

constructor(network?: Networkish, provider?: Web3Provider) {
const net = getNetwork(network)
const connectionInfo = getUrl(net, network)

super(connectionInfo)
this._ethereum = provider

// Handle properly deriving "from" on the transaction
const format = this.formatter.transaction
this.formatter.transaction = (transaction) => {
const tx = format(transaction)
const sig = joinSignature(tx as SignatureLike)
const hash = utils.sighashEthSign(tx)
// need to concat and hash with
tx.from = verifyMessage(hash, sig)
return tx
}
}

public get ethereum() {
return this._ethereum
}

public getSigner(address?: string): OptimismSigner {
if (this.ethereum) {
return new OptimismSigner(this.ethereum, this, address)
}

logger.throwError(
'no web3 instance provided',
Logger.errors.UNSUPPORTED_OPERATION,
{
operation: 'getSigner',
}
)
}

// `send` takes the literal RPC method name. The signer cannot use this
// codepath, it is for querying an optimism node.
public async send(method: string, params: any[]): Promise<any> {
// Prevent certain calls from hitting the public nodes
if (utils.isBlacklistedMethod(method)) {
logger.throwError(
'blacklisted operation',
Logger.errors.UNSUPPORTED_OPERATION,
{
operation: method,
}
)
}

return super.send(method, params)
}

public prepareRequest(method: string, params: any): [string, any[]] {
switch (method) {
case 'sendTransaction':
case 'sendEthSignTransaction':
return ['eth_sendRawEthSignTransaction', [params.signedTransaction]]
}

return super.prepareRequest(method, params)
}

public async perform(method: string, params: any): Promise<any> {
return super.perform(method, params)
}
}
Loading