Commit 3a726175 authored by Matthew Slipper's avatar Matthew Slipper

Merge branch 'develop' into fix/eip2930

parents a5254658 d14ee9fa
---
'@eth-optimism/integration-tests': minor
---
Updates to work with a live network
---
'@eth-optimism/batch-submitter-service': patch
---
Adds confirmation depth awareness to txmgr
---
'@eth-optimism/proxyd': minor
---
proxyd: Cache block-dependent RPCs
......@@ -2,4 +2,4 @@
'@eth-optimism/proxyd': minor
---
Add integration tests and batching
Add debug cache status header to proxyd responses
---
'@eth-optimism/integration-tests': patch
---
Use hardhat-ethers for importing factories in integration tests
---
'@eth-optimism/l2geth': patch
---
Add reinitialize-by-url command, add dump chain state command
---
'@eth-optimism/integration-tests': patch
---
Split OVMMulticall.sol into Multicall.sol & OVMContext.sol
---
'@eth-optimism/l2geth': patch
---
Fix blocknumber monotonicity logging bug
......@@ -101,7 +101,17 @@ module.exports = {
'id-match': 'off',
'import/no-extraneous-dependencies': ['error'],
'import/no-internal-modules': 'off',
'import/order': 'off',
'import/order': [
"error",
{
groups: [
'builtin',
'external',
'internal',
],
'newlines-between': 'always',
},
],
indent: 'off',
'jsdoc/check-alignment': 'error',
'jsdoc/check-indentation': 'error',
......
......@@ -6,6 +6,8 @@ on:
- 'master'
- 'develop'
pull_request:
paths:
- 'go/proxyd/**'
workflow_dispatch:
defaults:
......@@ -13,7 +15,9 @@ defaults:
working-directory: ./go/proxyd
jobs:
test:
tests:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
......
......@@ -29,6 +29,7 @@ jobs:
rpc-proxy : ${{ steps.packages.outputs.rpc-proxy }}
op-exporter : ${{ steps.packages.outputs.op-exporter }}
l2geth-exporter : ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service : ${{ steps.packages.outputs.batch-submitter-service }}
steps:
- name: Check out source code
......@@ -506,3 +507,29 @@ jobs:
file: ./ops/docker/Dockerfile.rpc-proxy
push: true
tags: ethereumoptimism/rpc-proxy:${{ needs.canary-publish.outputs.rpc-proxy }}
batch-submitter-service:
name: Publish batch-submitter-service Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.batch-submitter-service != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.batch-submitter-service
push: true
tags: ethereumoptimism/batch-submitter-service:${{ needs.canary-publish.outputs.batch-submitter-service }}
......@@ -25,6 +25,7 @@ jobs:
hardhat-node: ${{ steps.packages.outputs.hardhat-node }}
op-exporter : ${{ steps.packages.outputs.op-exporter }}
l2geth-exporter : ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service : ${{ steps.packages.outputs.batch-submitter-service }}
steps:
- name: Checkout Repo
......@@ -502,3 +503,29 @@ jobs:
push: true
tags: ethereumoptimism/replica-healthcheck:${{ needs.builder.outputs.replica-healthcheck }},ethereumoptimism/replica-healthcheck:latest
build-args: BUILDER_TAG=${{ needs.builder.outputs.builder }}
batch-submitter-service:
name: Publish batch-submitter-service Version ${{ needs.release.outputs.batch-submitter-service }}
needs: release
if: needs.release.outputs.batch-submitter-service != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.batch-submitter-service
push: true
tags: ethereumoptimism/batch-submitter-service:${{ needs.release.outputs.batch-submitter-service }},ethereumoptimism/batch-submitter-service:latest
# @eth-optimism/batch-submitter-service
## 0.1.0
### Minor Changes
- 356b7271: Add multi-tx support, clear pending txs on startup
### Patch Changes
- 85aa148d: Adds confirmation depth awareness to txmgr
## 0.0.2
### Patch Changes
- d6e0de5a: Fix metrics server
{
"name": "@eth-optimism/batch-submitter-service",
"version": "0.0.2",
"version": "0.1.0",
"private": true,
"devDependencies": {}
}
# @eth-optimism/proxyd
## 3.6.0
### Minor Changes
- 096c5f20: proxyd: Allow cached RPCs to be evicted by redis
- 71d64834: Add caching for block-dependent RPCs
- fd2e1523: proxyd: Cache block-dependent RPCs
- 1760613c: Add integration tests and batching
## 3.5.0
### Minor Changes
......
......@@ -2,6 +2,7 @@ package proxyd
import (
"context"
"time"
"github.com/go-redis/redis/v8"
"github.com/golang/snappy"
......@@ -16,6 +17,8 @@ type Cache interface {
const (
// assuming an average RPCRes size of 3 KB
memoryCacheLimit = 4096
// Set a large ttl to avoid expirations. However, a ttl must be set for volatile-lru to take effect.
redisTTL = 30 * 7 * 24 * time.Hour
)
type cache struct {
......@@ -67,7 +70,7 @@ func (c *redisCache) Get(ctx context.Context, key string) (string, error) {
}
func (c *redisCache) Put(ctx context.Context, key string, value string) error {
err := c.rdb.Set(ctx, key, value, 0).Err()
err := c.rdb.SetEX(ctx, key, value, redisTTL).Err()
if err != nil {
RecordRedisError("CacheSet")
}
......
......@@ -16,12 +16,14 @@ func TestCaching(t *testing.T) {
require.NoError(t, err)
defer redis.Close()
backend := NewMockBackend(RPCResponseHandler(map[string]string{
hdlr := NewRPCResponseHandler(map[string]string{
"eth_chainId": "0x420",
"net_version": "0x1234",
"eth_blockNumber": "0x64",
"eth_getBlockByNumber": "dummy_block",
}))
"eth_call": "dummy_call",
})
backend := NewMockBackend(hdlr)
defer backend.Close()
require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL()))
......@@ -36,19 +38,22 @@ func TestCaching(t *testing.T) {
time.Sleep(1500 * time.Millisecond)
tests := []struct {
method string
params []interface{}
response string
method string
params []interface{}
response string
backendCalls int
}{
{
"eth_chainId",
nil,
"{\"jsonrpc\": \"2.0\", \"result\": \"0x420\", \"id\": 999}",
1,
},
{
"net_version",
nil,
"{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 999}",
1,
},
{
"eth_getBlockByNumber",
......@@ -57,23 +62,79 @@ func TestCaching(t *testing.T) {
true,
},
"{\"jsonrpc\": \"2.0\", \"result\": \"dummy_block\", \"id\": 999}",
1,
},
{
"eth_call",
[]interface{}{
struct {
To string `json:"to"`
}{
"0x1234",
},
"0x60",
},
"{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}",
1,
},
{
"eth_blockNumber",
nil,
"{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x64\"}",
0,
},
{
"eth_call",
[]interface{}{
struct {
To string `json:"to"`
}{
"0x1234",
},
"latest",
},
"{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}",
2,
},
{
"eth_call",
[]interface{}{
struct {
To string `json:"to"`
}{
"0x1234",
},
"pending",
},
"{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}",
2,
},
}
for _, tt := range tests {
t.Run(tt.method, func(t *testing.T) {
_, _, err := client.SendRPC(tt.method, tt.params)
resRaw, _, err := client.SendRPC(tt.method, tt.params)
require.NoError(t, err)
res, _, err := client.SendRPC(tt.method, tt.params)
resCache, _, err := client.SendRPC(tt.method, tt.params)
require.NoError(t, err)
RequireEqualJSON(t, []byte(tt.response), res)
var count int
for _, req := range backend.Requests() {
if bytes.Contains(req.Body, []byte(tt.method)) {
count++
}
}
require.Equal(t, 1, count)
RequireEqualJSON(t, []byte(tt.response), resCache)
RequireEqualJSON(t, resRaw, resCache)
require.Equal(t, tt.backendCalls, countRequests(backend, tt.method))
backend.Reset()
})
}
hdlr.SetResponse("eth_blockNumber", "0x100")
time.Sleep(1500 * time.Millisecond)
resRaw, _, err := client.SendRPC("eth_blockNumber", nil)
RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x100\"}"), resRaw)
}
func countRequests(backend *MockBackend, name string) int {
var count int
for _, req := range backend.Requests() {
if bytes.Contains(req.Body, []byte(name)) {
count++
}
}
return count
}
......@@ -31,31 +31,48 @@ func SingleResponseHandler(code int, response string) http.HandlerFunc {
}
}
func RPCResponseHandler(rpcResponses map[string]string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
req, err := proxyd.ParseRPCReq(body)
if err != nil {
panic(err)
}
res := rpcResponses[req.Method]
if res == "" {
w.WriteHeader(400)
return
}
out := &proxyd.RPCRes{
JSONRPC: proxyd.JSONRPCVersion,
Result: res,
ID: req.ID,
}
enc := json.NewEncoder(w)
if err := enc.Encode(out); err != nil {
panic(err)
}
type RPCResponseHandler struct {
mtx sync.RWMutex
rpcResponses map[string]string
}
func NewRPCResponseHandler(rpcResponses map[string]string) *RPCResponseHandler {
return &RPCResponseHandler{
rpcResponses: rpcResponses,
}
}
func (h *RPCResponseHandler) SetResponse(method, response string) {
h.mtx.Lock()
defer h.mtx.Unlock()
h.rpcResponses[method] = response
}
func (h *RPCResponseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
req, err := proxyd.ParseRPCReq(body)
if err != nil {
panic(err)
}
h.mtx.RLock()
res := h.rpcResponses[req.Method]
h.mtx.RUnlock()
if res == "" {
w.WriteHeader(400)
return
}
out := &proxyd.RPCRes{
JSONRPC: proxyd.JSONRPCVersion,
Result: res,
ID: req.ID,
}
enc := json.NewEncoder(w)
if err := enc.Encode(out); err != nil {
panic(err)
}
}
......
......@@ -25,3 +25,5 @@ backends = ["good"]
eth_chainId = "main"
net_version = "main"
eth_getBlockByNumber = "main"
eth_blockNumber = "main"
eth_call = "main"
{
"name": "@eth-optimism/proxyd",
"version": "3.5.0",
"version": "3.6.0",
"private": true,
"dependencies": {}
}
......@@ -15,16 +15,46 @@ type RPCReq struct {
}
type RPCRes struct {
JSONRPC string
Result interface{}
Error *RPCErr
ID json.RawMessage
}
type rpcResJSON struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
Error *RPCErr `json:"error,omitempty"`
ID json.RawMessage `json:"id"`
}
type nullResultRPCRes struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result"`
ID json.RawMessage `json:"id"`
}
func (r *RPCRes) IsError() bool {
return r.Error != nil
}
func (r *RPCRes) MarshalJSON() ([]byte, error) {
if r.Result == nil && r.Error == nil {
return json.Marshal(&nullResultRPCRes{
JSONRPC: r.JSONRPC,
Result: nil,
ID: r.ID,
})
}
return json.Marshal(&rpcResJSON{
JSONRPC: r.JSONRPC,
Result: r.Result,
Error: r.Error,
ID: r.ID,
})
}
type RPCErr struct {
Code int `json:"code"`
Message string `json:"message"`
......
package proxyd
import (
"encoding/json"
"github.com/stretchr/testify/require"
"testing"
)
func TestRPCResJSON(t *testing.T) {
tests := []struct {
name string
in *RPCRes
out string
}{
{
"string result",
&RPCRes{
JSONRPC: JSONRPCVersion,
Result: "foobar",
ID: []byte("123"),
},
`{"jsonrpc":"2.0","result":"foobar","id":123}`,
},
{
"object result",
&RPCRes{
JSONRPC: JSONRPCVersion,
Result: struct {
Str string `json:"str"`
}{
"test",
},
ID: []byte("123"),
},
`{"jsonrpc":"2.0","result":{"str":"test"},"id":123}`,
},
{
"nil result",
&RPCRes{
JSONRPC: JSONRPCVersion,
Result: nil,
ID: []byte("123"),
},
`{"jsonrpc":"2.0","result":null,"id":123}`,
},
{
"error result",
&RPCRes{
JSONRPC: JSONRPCVersion,
Error: &RPCErr{
Code: 1234,
Message: "test err",
},
ID: []byte("123"),
},
`{"jsonrpc":"2.0","error":{"code":1234,"message":"test err"},"id":123}`,
},
{
"string ID",
&RPCRes{
JSONRPC: JSONRPCVersion,
Result: "foobar",
ID: []byte("\"123\""),
},
`{"jsonrpc":"2.0","result":"foobar","id":"123"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
out, err := json.Marshal(tt.in)
require.NoError(t, err)
require.Equal(t, tt.out, string(out))
})
}
}
......@@ -25,6 +25,7 @@ const (
ContextKeyReqID = "req_id"
ContextKeyXForwardedFor = "x_forwarded_for"
MaxBatchRPCCalls = 100
cacheStatusHdr = "X-Proxyd-Cache-Status"
)
type Server struct {
......@@ -159,6 +160,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
}
batchRes := make([]*RPCRes, len(reqs), len(reqs))
var batchContainsCached bool
for i := 0; i < len(reqs); i++ {
req, err := ParseRPCReq(reqs[i])
if err != nil {
......@@ -167,9 +169,14 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
continue
}
batchRes[i] = s.handleSingleRPC(ctx, req)
var cached bool
batchRes[i], cached = s.handleSingleRPC(ctx, req)
if cached {
batchContainsCached = true
}
}
setCacheHeader(w, batchContainsCached)
writeBatchRPCRes(ctx, w, batchRes)
return
}
......@@ -181,14 +188,15 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
return
}
backendRes := s.handleSingleRPC(ctx, req)
backendRes, cached := s.handleSingleRPC(ctx, req)
setCacheHeader(w, cached)
writeRPCRes(ctx, w, backendRes)
}
func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) (*RPCRes, bool) {
if err := ValidateRPCReq(req); err != nil {
RecordRPCError(ctx, BackendProxyd, MethodUnknown, err)
return NewRPCErrorRes(nil, err)
return NewRPCErrorRes(nil, err), false
}
group := s.rpcMethodMappings[req.Method]
......@@ -202,7 +210,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
"method", req.Method,
)
RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrMethodNotWhitelisted)
return NewRPCErrorRes(req.ID, ErrMethodNotWhitelisted)
return NewRPCErrorRes(req.ID, ErrMethodNotWhitelisted), false
}
var backendRes *RPCRes
......@@ -215,7 +223,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
)
}
if backendRes != nil {
return backendRes
return backendRes, true
}
backendRes, err = s.backendGroups[group].Forward(ctx, req)
......@@ -226,7 +234,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
"req_id", GetReqID(ctx),
"err", err,
)
return NewRPCErrorRes(req.ID, err)
return NewRPCErrorRes(req.ID, err), false
}
if backendRes.Error == nil {
......@@ -239,7 +247,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
}
}
return backendRes
return backendRes, false
}
func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
......@@ -322,6 +330,14 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
)
}
func setCacheHeader(w http.ResponseWriter, cached bool) {
if cached {
w.Header().Set(cacheStatusHdr, "HIT")
} else {
w.Header().Set(cacheStatusHdr, "MISS")
}
}
func writeRPCError(ctx context.Context, w http.ResponseWriter, id json.RawMessage, err error) {
var res *RPCRes
if r, ok := err.(*RPCErr); ok {
......
# @eth-optimism/integration-tests
## 0.5.0
### Minor Changes
- c1e923f9: Updates to work with a live network
### Patch Changes
- 968fb38d: Use hardhat-ethers for importing factories in integration tests
- a7fbafa8: Split OVMMulticall.sol into Multicall.sol & OVMContext.sol
## 0.4.2
### Patch Changes
......
import { utils, Wallet, BigNumber } from 'ethers'
import { expect } from 'chai'
import { setupActor, setupRun, actor, run } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
import { Direction } from '../test/shared/watcher-utils'
import { expect } from 'chai'
interface BenchContext {
l1Wallet: Wallet
......
import { performance } from 'perf_hooks'
import { Mutex } from 'async-mutex'
import { sleep } from '../../test/shared/utils'
import {
sanitizeForMetrics,
benchDurationsSummary,
......@@ -9,7 +11,7 @@ import {
failedBenchRunsTotal,
} from './metrics'
import { ActorLogger, WorkerLogger } from './logger'
import { performance } from 'perf_hooks'
import { sleep } from '../../test/shared/utils'
// eslint-disable-next-line @typescript-eslint/no-empty-function
const asyncNoop = async () => {}
......
import fs from 'fs'
import client from 'prom-client'
import http from 'http'
import url from 'url'
import client from 'prom-client'
export const metricsRegistry = new client.Registry()
const metricName = (name: string) => {
......
import * as path from 'path'
import { Command } from 'commander'
import { defaultRuntime } from './convenience'
import { RunOpts } from './actor'
import { Command } from 'commander'
import pkg from '../../package.json'
import { serveMetrics } from './metrics'
import pkg from '../../package.json'
const program = new Command()
program.version(pkg.version)
......
import { utils, Wallet, Contract } from 'ethers'
import { expect } from 'chai'
import { actor, run, setupActor, setupRun } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
import ERC721 from '../artifacts/contracts/NFT.sol/NFT.json'
import { expect } from 'chai'
interface Context {
wallet: Wallet
......
import { utils, Wallet, BigNumber } from 'ethers'
import { expect } from 'chai'
import { actor, setupRun, setupActor, run } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
......
import { utils, Wallet, Contract } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from 'chai'
import { actor, setupActor, run, setupRun } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
import { expect } from 'chai'
interface Context {
wallet: Wallet
......
import { Contract, utils, Wallet } from 'ethers'
import { actor, run, setupActor, setupRun } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
import { FeeAmount } from '@uniswap/v3-sdk'
import ERC20 from '../artifacts/contracts/ERC20.sol/ERC20.json'
import { abi as NFTABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { abi as RouterABI } from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'
import { actor, run, setupActor, setupRun } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
import ERC20 from '../artifacts/contracts/ERC20.sol/ERC20.json'
interface Context {
contracts: { [name: string]: Contract }
wallet: Wallet
......
{
"private": true,
"name": "@eth-optimism/integration-tests",
"version": "0.4.2",
"version": "0.5.0",
"description": "[Optimism] Integration tests",
"scripts": {
"lint": "yarn lint:fix && yarn lint:check",
......@@ -28,9 +28,9 @@
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"devDependencies": {
"@eth-optimism/contracts": "0.5.8",
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/message-relayer": "0.2.12",
"@eth-optimism/contracts": "0.5.9",
"@eth-optimism/core-utils": "0.7.4",
"@eth-optimism/message-relayer": "0.2.13",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0",
......
import { expect } from './shared/setup'
/* Imports: External */
import { Contract, ContractFactory } from 'ethers'
import { ethers } from 'hardhat'
import { applyL1ToL2Alias, awaitCondition } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { expect } from './shared/setup'
import { Direction } from './shared/watcher-utils'
import { OptimismEnv } from './shared/env'
import {
......
import { expect } from './shared/setup'
import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import * as L2Artifact from '@eth-optimism/contracts/artifacts/contracts/standards/L2StandardERC20.sol/L2StandardERC20.json'
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import { withdrawalTest } from './shared/utils'
import { Direction } from './shared/watcher-utils'
......
import { expect } from './shared/setup'
import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import { UniswapV3Deployer } from 'uniswap-v3-deploy-plugin/dist/deployer/UniswapV3Deployer'
import { OptimismEnv } from './shared/env'
import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { abi as NFTABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { abi as RouterABI } from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'
import { OptimismEnv } from './shared/env'
import { expect } from './shared/setup'
// Below methods taken from the Uniswap test suite, see
// https://github.com/Uniswap/v3-periphery/blob/main/test/shared/ticks.ts
const getMinTick = (tickSpacing: number) =>
......
import { expect } from './shared/setup'
/* Imports: External */
import { BigNumber, utils } from 'ethers'
import { serialize } from '@ethersproject/transactions'
import { predeploys, getContractFactory } from '@eth-optimism/contracts'
/* Imports: Internal */
import { expect } from './shared/setup'
import { hardhatTest } from './shared/utils'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
......
import { expect } from './shared/setup'
import { BigNumber, Contract, ContractFactory, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from './shared/setup'
import {
fundUser,
encodeSolidityRevertMessage,
......
import { expect } from './shared/setup'
/* Imports: External */
import { Wallet, utils, BigNumber } from 'ethers'
import { serialize } from '@ethersproject/transactions'
......@@ -7,8 +5,8 @@ import { predeploys } from '@eth-optimism/contracts'
import { expectApprox } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { expect } from './shared/setup'
import { Direction } from './shared/watcher-utils'
import {
DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2,
......
import { expect } from './shared/setup'
/* Imports: External */
import { ethers } from 'hardhat'
import { injectL2Context, expectApprox } from '@eth-optimism/core-utils'
......@@ -7,6 +5,7 @@ import { predeploys } from '@eth-optimism/contracts'
import { Contract, BigNumber } from 'ethers'
/* Imports: Internal */
import { expect } from './shared/setup'
import {
l2Provider,
l1Provider,
......
import { expect } from './shared/setup'
/* Imports: Internal */
import { ethers } from 'ethers'
import { predeploys, getContractInterface } from '@eth-optimism/contracts'
/* Imports: External */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
describe('predeploys', () => {
......
import { expect } from './shared/setup'
/* Imports: Internal */
import { providers } from 'ethers'
import { injectL2Context, applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Imports: External */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
import { DEFAULT_TEST_GAS_L1, envConfig } from './shared/utils'
......
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import {
......@@ -6,7 +8,6 @@ import {
sleep,
envConfig,
} from './shared/utils'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
describe('Replica Tests', () => {
let env: OptimismEnv
......
import { expect } from './shared/setup'
import { expectApprox, injectL2Context } from '@eth-optimism/core-utils'
import { Wallet, BigNumber, Contract, ContractFactory, constants } from 'ethers'
import { serialize } from '@ethersproject/transactions'
import { ethers } from 'hardhat'
import {
TransactionReceipt,
TransactionRequest,
} from '@ethersproject/providers'
import {
sleep,
l2Provider,
......@@ -16,10 +19,7 @@ import {
envConfig,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
import {
TransactionReceipt,
TransactionRequest,
} from '@ethersproject/providers'
import { expect } from './shared/setup'
describe('Basic RPC tests', () => {
let env: OptimismEnv
......
......@@ -4,7 +4,6 @@ import {
TransactionResponse,
} from '@ethersproject/providers'
import { Watcher } from '@eth-optimism/core-utils'
import { Contract, Transaction } from 'ethers'
export const initWatcher = async (
......
import { expect } from './shared/setup'
/* Imports: External */
import { Contract, Wallet, utils } from 'ethers'
import { ethers } from 'hardhat'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import {
executeL1ToL2TransactionsParallel,
......@@ -15,7 +14,6 @@ import {
executeRepeatedL2Transactions,
fundRandomWallet,
} from './shared/stress-test-helpers'
/* Imports: Artifacts */
import { envConfig, fundUser } from './shared/utils'
......
import { expect } from './shared/setup'
/* Imports: External */
import { ContractFactory } from 'ethers'
import { ethers } from 'hardhat'
import { predeploys } from '@eth-optimism/contracts'
/* Imports: Internal */
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import { l2Provider } from './shared/utils'
......
# Changelog
## 0.5.8
### Patch Changes
- 949916f8: Add a better error message for when the sequencer url is not configured when proxying user requests to the sequencer for `eth_sendRawTransaction` when running as a verifier/replica
- 300f79bf: Fix nonce issue
- ae96d784: Add reinitialize-by-url command, add dump chain state command
- c7569a16: Fix blocknumber monotonicity logging bug
## 0.5.7
### Patch Changes
......
......@@ -209,14 +209,13 @@ func (st *StateTransition) buyGas() error {
func (st *StateTransition) preCheck() error {
// Make sure this transaction's nonce is correct.
if st.msg.CheckNonce() {
if rcfg.UsingOVM {
if st.msg.QueueOrigin() == types.QueueOriginL1ToL2 {
return st.buyGas()
}
}
nonce := st.state.GetNonce(st.msg.From())
if nonce < st.msg.Nonce() {
if rcfg.UsingOVM {
// The nonce never increments for L1ToL2 txs
if st.msg.QueueOrigin() == types.QueueOriginL1ToL2 {
return st.buyGas()
}
}
return ErrNonceTooHigh
} else if nonce > st.msg.Nonce() {
return ErrNonceTooLow
......
......@@ -50,7 +50,10 @@ import (
"github.com/tyler-smith/go-bip39"
)
var errOVMUnsupported = errors.New("OVM: Unsupported RPC Method")
var (
errOVMUnsupported = errors.New("OVM: Unsupported RPC Method")
errNoSequencerURL = errors.New("sequencer transaction forwarding not configured")
)
const (
// defaultDialTimeout is default duration the service will wait on
......@@ -1680,7 +1683,11 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
}
if s.b.IsVerifier() {
client, err := dialSequencerClientWithTimeout(ctx, s.b.SequencerClientHttp())
sequencerURL := s.b.SequencerClientHttp()
if sequencerURL == "" {
return common.Hash{}, errNoSequencerURL
}
client, err := dialSequencerClientWithTimeout(ctx, sequencerURL)
if err != nil {
return common.Hash{}, err
}
......
{
"name": "@eth-optimism/l2geth",
"version": "0.5.7",
"version": "0.5.8",
"private": true,
"devDependencies": {}
}
......@@ -32,6 +32,10 @@ docker-compose \
*Note*: This generates a large amount of log data which docker stores by default. See [Disk Usage](#disk-usage).
Also note that Docker Desktop only allocates 2GB of memory by default, which isn't enough to run the docker-compose services reliably.
To allocate more memory, go to Settings > Resources in the Docker UI and use the slider to change the value (_4GB recommended_). Make sure to click Apply & Restart for the changes to take effect.
To start the stack with monitoring enabled, just add the metric composition file.
```
docker-compose \
......
# Changelog
## 0.4.14
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
- @eth-optimism/contracts@0.5.9
## 0.4.13
### Patch Changes
......
{
"private": true,
"name": "@eth-optimism/batch-submitter",
"version": "0.4.13",
"version": "0.4.14",
"description": "[Optimism] Service for submitting transactions and transaction results",
"main": "dist/index",
"types": "dist/index",
......@@ -34,8 +34,8 @@
},
"dependencies": {
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.8",
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/contracts": "0.5.9",
"@eth-optimism/core-utils": "0.7.4",
"@eth-optimism/ynatm": "^0.2.2",
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/providers": "^5.4.5",
......
......@@ -14,6 +14,7 @@ import { Gauge, Histogram, Counter } from 'prom-client'
import { RollupInfo, sleep } from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
import { getContractFactory } from 'old-contracts'
/* Internal Imports */
import { TxSubmissionHooks } from '..'
......
......@@ -12,8 +12,8 @@ import {
import { Logger, Metrics } from '@eth-optimism/common-ts'
/* Internal Imports */
import { BlockRange, BatchSubmitter } from '.'
import { TransactionSubmitter } from '../utils'
import { BlockRange, BatchSubmitter } from '.'
export class StateBatchSubmitter extends BatchSubmitter {
// TODO: Change this so that we calculate start = scc.totalElements() and end = ctc.totalElements()!
......
......@@ -20,9 +20,8 @@ import {
BatchContext,
AppendSequencerBatchParams,
} from '../transaction-chain-contract'
import { BlockRange, BatchSubmitter } from '.'
import { TransactionSubmitter } from '../utils'
import { BlockRange, BatchSubmitter } from '.'
export interface AutoFixBatchOptions {
fixDoublePlayedDeposits: boolean
......
/* External Imports */
import { exit } from 'process'
import { injectL2Context, Bcfg } from '@eth-optimism/core-utils'
import * as Sentry from '@sentry/node'
import { Logger, Metrics, createMetricsServer } from '@eth-optimism/common-ts'
import { exit } from 'process'
import { Signer, Wallet } from 'ethers'
import {
StaticJsonRpcProvider,
......
import { expect } from '../setup'
/* External Imports */
import { ethers } from 'hardhat'
import '@nomiclabs/hardhat-ethers'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import sinon from 'sinon'
import scc from '@eth-optimism/contracts/artifacts/contracts/L1/rollup/StateCommitmentChain.sol/StateCommitmentChain.json'
import { getContractInterface } from '@eth-optimism/contracts'
import { smockit, MockContract } from '@eth-optimism/smock'
import { getContractFactory } from 'old-contracts'
import { QueueOrigin, Batch, remove0x } from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
/* Internal Imports */
import { MockchainProvider } from './mockchain-provider'
import {
makeAddressManager,
setProxyTarget,
FORCE_INCLUSION_PERIOD_SECONDS,
} from '../helpers'
import { expect } from '../setup'
import {
CanonicalTransactionChainContract,
TransactionBatchSubmitter as RealTransactionBatchSubmitter,
......@@ -28,9 +22,11 @@ import {
YnatmTransactionSubmitter,
ResubmissionConfig,
} from '../../src'
import { QueueOrigin, Batch, remove0x } from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
import {
makeAddressManager,
setProxyTarget,
FORCE_INCLUSION_PERIOD_SECONDS,
} from '../helpers'
const EXAMPLE_STATE_ROOT =
'0x16b7f83f409c7195b1f4fde5652f1b54a4477eacb6db7927691becafba5f8801'
......
import { expect } from '../setup'
import { ethers, BigNumber, Signer } from 'ethers'
import { submitTransactionWithYNATM } from '../../src/utils/tx-submission'
import { ResubmissionConfig } from '../../src'
import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { expect } from '../setup'
import { submitTransactionWithYNATM } from '../../src/utils/tx-submission'
import { ResubmissionConfig } from '../../src'
const nullFunction = () => undefined
const nullHooks = {
beforeSendTransaction: nullFunction,
......
import { Server } from 'net'
import prometheus, {
collectDefaultMetrics,
DefaultMetricsCollectorConfiguration,
Registry,
} from 'prom-client'
import express from 'express'
import { Server } from 'net'
import { Logger } from './logger'
......
# Changelog
## 0.5.9
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
## 0.5.8
### Patch Changes
......
/* External Imports */
import * as fs from 'fs'
import * as path from 'path'
import * as mkdirp from 'mkdirp'
const ensure = (value, key) => {
......
/* eslint @typescript-eslint/no-var-requires: "off" */
import { ethers } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { awaitCondition } from '@eth-optimism/core-utils'
import {
getContractFromArtifact,
fundAccount,
......@@ -8,7 +10,6 @@ import {
BIG_BALANCE,
} from '../src/deploy-utils'
import { names } from '../src/address-names'
import { awaitCondition } from '@eth-optimism/core-utils'
const deployFn: DeployFunction = async (hre) => {
if ((hre as any).deployConfig.forked !== 'true') {
......
/* Imports: Internal */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { names } from '../src/address-names'
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deploy } = hre.deployments
......
{
"name": "@eth-optimism/contracts",
"version": "0.5.8",
"version": "0.5.9",
"description": "[Optimism] L1 and L2 smart contracts for Optimism",
"main": "dist/index",
"types": "dist/index",
......@@ -58,7 +58,7 @@
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/core-utils": "0.7.4",
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/abstract-signer": "^5.4.1",
"@ethersproject/hardware-wallets": "^5.4.0"
......
import path from 'path'
import glob from 'glob'
import fs from 'fs'
import glob from 'glob'
/**
* Script for automatically generating a file which has a series of `require` statements for
* importing JSON contract artifacts. We do this to preserve browser compatibility.
......
import path from 'path'
import glob from 'glob'
import fs from 'fs'
import glob from 'glob'
/**
* Script for automatically generating a TypeScript file for retrieving deploy artifact JSON files.
* We do this to make sure that this package remains browser compatible.
......
import dirtree from 'directory-tree'
import fs from 'fs'
import path from 'path'
import dirtree from 'directory-tree'
import { predeploys } from '../src'
interface DeploymentInfo {
......
/* External Imports */
import { promisify } from 'util'
import { exec } from 'child_process'
import { promisify } from 'util'
import { ethers } from 'ethers'
import {
computeStorageSlots,
......
import { createInterface } from 'readline'
import { hexStringEquals } from '@eth-optimism/core-utils'
export const getInput = (query) => {
......
......@@ -4,9 +4,9 @@ import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
import { hexStringEquals } from '@eth-optimism/core-utils'
import { getContractFactory, getContractDefinition } from '../src/contract-defs'
import { names } from '../src/address-names'
import {
getInput,
color as c,
......
......@@ -3,8 +3,8 @@
import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
import { getContractFactory, getContractDefinition } from '../src/contract-defs'
import { getContractFactory, getContractDefinition } from '../src/contract-defs'
import {
getInput,
color as c,
......
'use strict'
import fs from 'fs'
import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
import { LedgerSigner } from '@ethersproject/hardware-wallets'
import { getContractFactory } from '../src/contract-defs'
import { predeploys } from '../src/predeploys'
......
......@@ -4,6 +4,7 @@ import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
import { LedgerSigner } from '@ethersproject/hardware-wallets'
import { getContractFactory } from '../src/contract-defs'
import { predeploys } from '../src/predeploys'
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
......@@ -11,6 +9,7 @@ import {
} from '@eth-optimism/core-utils'
/* Internal Imports */
import { expect } from '../../../setup'
import {
makeAddressManager,
setProxyTarget,
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, constants } from 'ethers'
......@@ -7,6 +5,7 @@ import { Interface } from 'ethers/lib/utils'
import { smockit, MockContract, smoddit } from '@eth-optimism/smock'
/* Internal Imports */
import { expect } from '../../../setup'
import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../../helpers'
import { getContractInterface, predeploys } from '../../../../src'
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract } from 'ethers'
......@@ -13,6 +11,7 @@ import { keccak256 } from 'ethers/lib/utils'
import _ from 'lodash'
/* Internal Imports */
import { expect } from '../../../setup'
import {
makeAddressManager,
setProxyTarget,
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, constants } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
/* Internal Imports */
import { expect } from '../../../setup'
import {
makeAddressManager,
setProxyTarget,
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, Contract } from 'ethers'
/* Internal Imports */
import { expect } from '../../../setup'
import { makeAddressManager } from '../../../helpers'
describe('BondManager', () => {
......
import { expect } from '../../../setup'
/* External Imports */
import hre, { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract } from 'ethers'
......@@ -7,6 +5,7 @@ import { smockit, MockContract } from '@eth-optimism/smock'
import { applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Internal Imports */
import { expect } from '../../../setup'
import {
NON_NULL_BYTES32,
NON_ZERO_ADDRESS,
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract } from 'ethers'
......@@ -11,8 +9,8 @@ import {
} from '@eth-optimism/smock'
/* Internal Imports */
import { expect } from '../../../setup'
import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../../helpers'
import { getContractInterface } from '../../../../src'
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract } from 'ethers'
import { smoddit } from '@eth-optimism/smock'
/* Internal Imports */
import { expect } from '../../../setup'
import { predeploys, getContractInterface } from '../../../../src'
describe('L2StandardTokenFactory', () => {
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { ContractFactory, Contract, Signer } from 'ethers'
import { expect } from '../../../setup'
describe('OVM_ETH', () => {
let signer1: Signer
let signer2: Signer
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { ContractFactory, Contract, Signer } from 'ethers'
import { calculateL1GasUsed, calculateL1Fee } from '@eth-optimism/core-utils'
import { expect } from '../../../setup'
describe('OVM_GasPriceOracle', () => {
const initialGasPrice = 0
let signer1: Signer
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { ContractFactory, Contract } from 'ethers'
......@@ -8,6 +6,7 @@ import { remove0x } from '@eth-optimism/core-utils'
import { keccak256 } from 'ethers/lib/utils'
/* Internal Imports */
import { expect } from '../../../setup'
import { NON_ZERO_ADDRESS } from '../../../helpers/constants'
const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16]
......
import { expect } from '../../../setup'
/* Imports: External */
import hre from 'hardhat'
import { MockContract, smockit } from '@eth-optimism/smock'
import { Contract, Signer } from 'ethers'
/* Imports: Internal */
import { expect } from '../../../setup'
import { predeploys } from '../../../../src'
describe('OVM_SequencerFeeVault', () => {
......
import { expect } from '../../setup'
/* Imports: External */
import hre from 'hardhat'
import { Contract, Signer } from 'ethers'
import { smockit } from '@eth-optimism/smock'
/* Imports: Internal */
import { expect } from '../../setup'
import { getContractInterface } from '../../../src'
describe('L1ChugSplashProxy', () => {
......
import '../../../setup'
/* Internal Imports */
import '../../../setup'
import { Lib_OVMCodec_TEST_JSON } from '../../../data'
import { runJsonTest } from '../../../helpers'
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract } from 'ethers'
/* Internal Imports */
import { expect } from '../../../setup'
import { Lib_RLPWriter_TEST_JSON } from '../../../data'
const encode = async (Lib_RLPWriter: Contract, input: any): Promise<void> => {
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract } from 'ethers'
import { applyL1ToL2Alias, undoL1ToL2Alias } from '@eth-optimism/core-utils'
import { expect } from '../../../setup'
describe('AddressAliasHelper', () => {
let AddressAliasHelper: Contract
before(async () => {
......
import { expect } from '../../../setup'
/* External Imports */
import * as rlp from 'rlp'
import { ethers } from 'hardhat'
......@@ -8,6 +6,7 @@ import { fromHexString, toHexString } from '@eth-optimism/core-utils'
import { Trie } from 'merkle-patricia-tree/dist/baseTrie'
/* Internal Imports */
import { expect } from '../../../setup'
import { TrieTestGenerator } from '../../../helpers'
import * as officialTestJson from '../../../data/json/libraries/trie/trietest.json'
import * as officialTestAnyOrderJson from '../../../data/json/libraries/trie/trieanyorder.json'
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract } from 'ethers'
/* Internal Imports */
import { expect } from '../../../setup'
import { TrieTestGenerator } from '../../../helpers'
const NODE_COUNTS = [1, 2, 128]
......
import { expect } from '../../../setup'
import hre from 'hardhat'
import { Contract, ethers } from 'ethers'
import { expect } from '../../../setup'
describe('Lib_Buffer', () => {
let Lib_Buffer: Contract
beforeEach(async () => {
......
/* Internal Imports */
import { Lib_BytesUtils_TEST_JSON } from '../../../data'
import { runJsonTest } from '../../../helpers'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract } from 'ethers'
/* External Imports */
import { Lib_BytesUtils_TEST_JSON } from '../../../data'
import { runJsonTest } from '../../../helpers'
import { expect } from '../../../setup'
describe('Lib_BytesUtils', () => {
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract, BigNumber } from 'ethers'
......@@ -7,6 +5,7 @@ import { MerkleTree } from 'merkletreejs'
import { fromHexString, toHexString } from '@eth-optimism/core-utils'
/* Internal Imports */
import { expect } from '../../../setup'
import { NON_NULL_BYTES32 } from '../../../helpers'
const NODE_COUNTS = [
......
import { NON_ZERO_ADDRESS } from '../constants'
import { ethers } from 'hardhat'
import { NON_ZERO_ADDRESS } from '../constants'
export const DUMMY_BATCH_HEADERS = [
{
batchIndex: 0,
......
import { expect } from '../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract, BigNumber } from 'ethers'
import { expect } from '../../setup'
const bigNumberify = (arr: any[]) => {
return arr.map((el: any) => {
if (typeof el === 'number') {
......
# @eth-optimism/core-utils
## 0.7.4
### Patch Changes
- ba96a455: Improved docstrings for BCFG typings
- c3e85fef: Cleans up the internal file and folder structure for the typings exported by core-utils
## 0.7.3
### Patch Changes
......
{
"name": "@eth-optimism/core-utils",
"version": "0.7.3",
"version": "0.7.4",
"description": "[Optimism] Core typescript utilities",
"main": "dist/index",
"types": "dist/index",
......
import { ethers } from 'ethers'
import { bnToAddress } from './bn'
// Constant representing the alias to apply to the msg.sender when a contract sends an L1 => L2
......
export interface Bcfg {
load: (options: { env?: boolean; argv?: boolean }) => void
str: (name: string, defaultValue?: string) => string
uint: (name: string, defaultValue?: number) => number
bool: (name: string, defaultValue?: boolean) => boolean
ufloat: (name: string, defaultValue?: number) => number
has: (name: string) => boolean
}
import { ethers } from 'ethers'
import { remove0x, add0x } from './common/hex-strings'
/**
......
export * from './types'
export * from './sequencer-batch'
import { add0x, remove0x, encodeHex } from '../common'
import { BigNumber, ethers } from 'ethers'
import { add0x, remove0x, encodeHex } from '../common'
export interface BatchContext {
numSequencedTransactions: number
numSubsequentQueueTransactions: number
......
import { expect } from 'chai'
import { BigNumber } from 'ethers'
import { sleep } from './misc'
interface deviationRanges {
......
......@@ -3,6 +3,7 @@
*/
import { BigNumber } from 'ethers'
import { remove0x } from './common'
const txDataZeroGas = 4
......
......@@ -2,8 +2,7 @@ export * from './coders'
export * from './common'
export * from './watcher'
export * from './l2context'
export * from './batches'
export * from './bcfg'
export * from './types'
export * from './fees'
export * from './provider'
export * from './alias'
......
// Use this file for simple types that aren't necessarily associated with a specific project or
// package. Often used for alias types like Address = string.
export interface Signature {
r: string
s: string
......
/**
* TypeScript typings for bcoin's BCFG config parser (https://github.com/bcoin-org/bcfg)
* This is NOT a complete set of typings, just what we use at Optimism at the moment.
* We could consider expanding this into a full set of typings in the future.
*/
export interface Bcfg {
/**
* Loads configuration values from the environment. Must be called before environment variables
* can be accessed with other methods like str(...) or uint(...).
*
* @param options Options to use when loading arguments.
* @param options.env Boolean, whether or not to load from process.env.
* @param options.argv Boolean, whether or not to load from process.argv.
*/
load: (options: { env?: boolean; argv?: boolean }) => void
/**
* Returns the variable with the given name and casts it as a string. Queries from the
* environment or from argv depending on which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a string.
*/
str: (name: string, defaultValue?: string) => string
/**
* Returns the variable with the given name and casts it as a uint. Will throw an error if the
* variable cannot be cast into a uint. Queries from the environment or from argv depending on
* which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a uint.
*/
uint: (name: string, defaultValue?: number) => number
/**
* Returns the variable with the given name and casts it as a bool. Will throw an error if the
* variable cannot be cast into a bool. Queries from the environment or from argv depending on
* which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a bool.
*/
bool: (name: string, defaultValue?: boolean) => boolean
/**
* Returns the variable with the given name and casts it as a ufloat. Will throw an error if the
* variable cannot be cast into a ufloat. Queries from the environment or from argv depending on
* which were loaded when load() was called.
*
* @param name Name of the variable to query.
* @param defaultValue Optional default value if the variable does not exist.
* @returns Variable cast to a ufloat.
*/
ufloat: (name: string, defaultValue?: number) => number
/**
* Checks if the given variable exists.
*
* @param name Name of the variable to query.
* @returns True if the variable exists, false otherwise.
*/
has: (name: string) => boolean
}
// Optimism PBC 2021
// Types explicitly related to dealing with Geth.
// Represents the ethereum state
/**
* Represents the Ethereum state, in the format that Geth expects it.
*/
export interface State {
[address: string]: {
nonce: number
......@@ -14,7 +16,9 @@ export interface State {
}
}
// Represents a genesis file that geth can consume
/**
* Represents Geth's genesis file format.
*/
export interface Genesis {
config: {
chainId: number
......
export * from './geth'
export * from './bcfg'
export * from './rollup'
export * from './basic'
......@@ -4,6 +4,9 @@ import {
TransactionResponse,
} from '@ethersproject/abstract-provider'
/**
* Structure of the response returned by L2Geth nodes when querying the `rollup_getInfo` endpoint.
*/
export interface RollupInfo {
mode: 'sequencer' | 'verifier'
syncing: boolean
......@@ -17,14 +20,18 @@ export interface RollupInfo {
}
}
/**
* Enum used for the two transaction types (queue and direct to Sequencer).
*/
export enum QueueOrigin {
Sequencer = 'sequencer',
L1ToL2 = 'l1',
}
/**
* Transaction & Blocks. These are the true data-types we expect
* from running a batch submitter.
* JSON transaction representation when returned by L2Geth nodes. This is simply an extension to
* the standard transaction response type. You do NOT need to use this type unless you care about
* having typed access to L2-specific fields.
*/
export interface L2Transaction extends TransactionResponse {
l1BlockNumber: number
......@@ -33,21 +40,32 @@ export interface L2Transaction extends TransactionResponse {
rawTransaction: string
}
/**
* JSON block representation when returned by L2Geth nodes. Just a normal block but with
* L2Transaction objects instead of the standard transaction response object.
*/
export interface L2Block extends BlockWithTransactions {
stateRoot: string
transactions: [L2Transaction]
}
/**
* BatchElement & Batch. These are the data-types of the compressed / batched
* block data we submit to L1.
* Generic batch element, either a state root batch element or a transaction batch element.
*/
export interface BatchElement {
// Only exists on state root batch elements.
stateRoot: string
// Only exists on transaction batch elements.
isSequencerTx: boolean
rawTransaction: undefined | string
// Batch element context, exists on all batch elements.
timestamp: number
blockNumber: number
}
/**
* List of batch elements.
*/
export type Batch = BatchElement[]
import './setup'
/* Internal Imports */
import { expect } from 'chai'
import {
encodeAppendSequencerBatch,
decodeAppendSequencerBatch,
sequencerBatch,
} from '../src'
import { expect } from 'chai'
describe('BatchEncoder', () => {
describe('appendSequencerBatch', () => {
......
import { expect } from './setup'
import { BigNumber } from 'ethers'
/* Imports: Internal */
import { expect } from './setup'
import {
toRpcHexString,
remove0x,
......
import { expect } from './setup'
/* Imports: Internal */
import { expect } from './setup'
import { sleep } from '../src'
describe('sleep', async () => {
......
import { expect } from './setup'
import { assert } from 'chai'
/* Imports: Internal */
import { expect } from './setup'
import { expectApprox, awaitCondition } from '../src'
import { assert } from 'chai'
describe('awaitCondition', () => {
it('should try the condition fn until it returns true', async () => {
......
# data transport layer
## 0.5.12
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
- @eth-optimism/contracts@0.5.9
## 0.5.11
### Patch Changes
......
{
"private": true,
"name": "@eth-optimism/data-transport-layer",
"version": "0.5.11",
"version": "0.5.12",
"description": "[Optimism] Service for shuttling data from L1 into L2",
"main": "dist/index",
"types": "dist/index",
......@@ -37,8 +37,8 @@
},
"dependencies": {
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.8",
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/contracts": "0.5.9",
"@eth-optimism/core-utils": "0.7.4",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0",
"@sentry/node": "^6.3.1",
......
......@@ -3,6 +3,7 @@ import { LevelUp } from 'levelup'
import { BigNumber } from 'ethers'
/* Imports: Internal */
import { SimpleDB } from './simple-db'
import {
EnqueueEntry,
StateRootBatchEntry,
......@@ -10,7 +11,6 @@ import {
TransactionBatchEntry,
TransactionEntry,
} from '../types/database-types'
import { SimpleDB } from './simple-db'
const TRANSPORT_DB_KEYS = {
ENQUEUE: `enqueue`,
......
......@@ -9,6 +9,7 @@ import {
import { SequencerBatchAppendedEvent } from '@eth-optimism/contracts/dist/types/CanonicalTransactionChain'
/* Imports: Internal */
import { MissingElementError } from './errors'
import {
DecodedSequencerBatchTransaction,
SequencerBatchAppendedExtraData,
......@@ -18,7 +19,6 @@ import {
EventHandlerSet,
} from '../../../types'
import { SEQUENCER_GAS_LIMIT, parseSignatureVParam } from '../../../utils'
import { MissingElementError } from './errors'
export const handleEventsSequencerBatchAppended: EventHandlerSet<
SequencerBatchAppendedEvent,
......
......@@ -4,6 +4,7 @@ import { getContractFactory } from '@eth-optimism/contracts'
import { BigNumber } from 'ethers'
/* Imports: Internal */
import { MissingElementError } from './errors'
import {
StateRootBatchEntry,
StateBatchAppendedExtraData,
......@@ -11,7 +12,6 @@ import {
StateRootEntry,
EventHandlerSet,
} from '../../../types'
import { MissingElementError } from './errors'
export const handleEventsStateBatchAppended: EventHandlerSet<
StateBatchAppendedEvent,
......
......@@ -3,8 +3,8 @@ import { BigNumber } from 'ethers'
import { TransactionEnqueuedEvent } from '@eth-optimism/contracts/dist/types/CanonicalTransactionChain'
/* Imports: Internal */
import { EnqueueEntry, EventHandlerSet } from '../../../types'
import { MissingElementError } from './errors'
import { EnqueueEntry, EventHandlerSet } from '../../../types'
export const handleEventsTransactionEnqueued: EventHandlerSet<
TransactionEnqueuedEvent,
......
......@@ -8,6 +8,10 @@ import { constants } from 'ethers'
import { Gauge, Counter } from 'prom-client'
/* Imports: Internal */
import { handleEventsTransactionEnqueued } from './handlers/transaction-enqueued'
import { handleEventsSequencerBatchAppended } from './handlers/sequencer-batch-appended'
import { handleEventsStateBatchAppended } from './handlers/state-batch-appended'
import { MissingElementError } from './handlers/errors'
import { TransportDB } from '../../db/transport-db'
import {
OptimismContracts,
......@@ -17,11 +21,7 @@ import {
validators,
} from '../../utils'
import { EventHandlerSet } from '../../types'
import { handleEventsTransactionEnqueued } from './handlers/transaction-enqueued'
import { handleEventsSequencerBatchAppended } from './handlers/sequencer-batch-appended'
import { handleEventsStateBatchAppended } from './handlers/state-batch-appended'
import { L1DataTransportServiceOptions } from '../main/service'
import { MissingElementError } from './handlers/errors'
interface L1IngestionMetrics {
highestSyncedL1Block: Gauge<string>
......
......@@ -8,10 +8,10 @@ import bfj from 'bfj'
import { Gauge } from 'prom-client'
/* Imports: Internal */
import { handleSequencerBlock } from './handlers/transaction'
import { TransportDB } from '../../db/transport-db'
import { sleep, toRpcHexString, validators } from '../../utils'
import { L1DataTransportServiceOptions } from '../main/service'
import { handleSequencerBlock } from './handlers/transaction'
interface L2IngestionMetrics {
highestSyncedL2Block: Gauge<string>
......
......@@ -2,13 +2,13 @@
import { BaseService, Metrics } from '@eth-optimism/common-ts'
import { LevelUp } from 'levelup'
import level from 'level'
import { Counter } from 'prom-client'
/* Imports: Internal */
import { L1IngestionService } from '../l1-ingestion/service'
import { L1TransportServer } from '../server/service'
import { validators } from '../../utils'
import { L2IngestionService } from '../l2-ingestion/service'
import { Counter } from 'prom-client'
export interface L1DataTransportServiceOptions {
nodeEnv: string
......
......@@ -2,13 +2,13 @@ import { BaseProvider } from '@ethersproject/providers'
import { BigNumber } from 'ethers'
import { TypedEvent } from '@eth-optimism/contracts/dist/types/common'
import { TransportDB } from '../db/transport-db'
import {
TransactionBatchEntry,
TransactionEntry,
StateRootBatchEntry,
StateRootEntry,
} from './database-types'
import { TransportDB } from '../db/transport-db'
export type GetExtraDataHandler<TEvent extends TypedEvent, TExtraData> = (
event?: TEvent,
......
import { fromHexString } from '@eth-optimism/core-utils'
import * as url from 'url'
import { fromHexString } from '@eth-optimism/core-utils'
export const validators = {
isBoolean: (val: any): boolean => {
return typeof val === 'boolean'
......
import { expect } from '../../../../setup'
/* Imports: External */
import { BigNumber } from 'ethers'
import { Block } from '@ethersproject/abstract-provider'
/* Imports: Internal */
import { expect } from '../../../../setup'
import { handleEventsStateBatchAppended } from '../../../../../src/services/l1-ingestion/handlers/state-batch-appended'
import { StateBatchAppendedExtraData } from '../../../../../src/types'
import { l1StateBatchData } from '../../../examples/l1-data'
......
import { expect } from '../../../../setup'
/* Imports: External */
import { ethers, BigNumber } from 'ethers'
/* Imports: Internal */
import { expect } from '../../../../setup'
import { handleEventsTransactionEnqueued } from '../../../../../src/services/l1-ingestion/handlers/transaction-enqueued'
const MAX_ITERATIONS = 128
......
import { expect } from '../../../../setup'
/* Imports: Internal */
import { expect } from '../../../../setup'
import { l2Block } from '../../../examples/l2-data'
import { handleSequencerBlock } from '../../../../../src/services/l2-ingestion/handlers/transaction'
......
# @eth-optimism/message-relayer
## 0.2.13
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
- @eth-optimism/contracts@0.5.9
## 0.2.12
### Patch Changes
......
{
"name": "@eth-optimism/message-relayer",
"version": "0.2.12",
"version": "0.2.13",
"description": "[Optimism] Service for automatically relaying L2 to L1 transactions",
"main": "dist/index",
"types": "dist/index",
......@@ -35,8 +35,8 @@
},
"dependencies": {
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.8",
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/contracts": "0.5.9",
"@eth-optimism/core-utils": "0.7.4",
"@sentry/node": "^6.3.1",
"bcfg": "^0.1.6",
"dotenv": "^10.0.0",
......
import { Wallet, providers } from 'ethers'
import { MessageRelayerService } from '../service'
import { Bcfg } from '@eth-optimism/core-utils'
import { Logger, LoggerOptions } from '@eth-optimism/common-ts'
import * as Sentry from '@sentry/node'
import * as dotenv from 'dotenv'
import Config from 'bcfg'
import { MessageRelayerService } from '../service'
dotenv.config()
const main = async () => {
......
......@@ -2,16 +2,15 @@
import { Contract, ethers, Wallet, BigNumber, providers } from 'ethers'
import * as rlp from 'rlp'
import { MerkleTree } from 'merkletreejs'
/* Imports: Internal */
import { fromHexString, sleep } from '@eth-optimism/core-utils'
import { Logger, BaseService, Metrics } from '@eth-optimism/common-ts'
import {
loadContract,
loadContractFromManager,
predeploys,
} from '@eth-optimism/contracts'
/* Imports: Internal */
import { StateRootBatchHeader, SentMessage, SentMessageProof } from './types'
interface MessageRelayerOptions {
......
import { expect } from '../setup'
/* Imports: External */
import hre from 'hardhat'
import { Contract, Signer } from 'ethers'
......@@ -8,6 +6,7 @@ import { smockit } from '@eth-optimism/smock'
import { toPlainObject } from 'lodash'
/* Imports: Internal */
import { expect } from '../setup'
import {
getMerkleTreeProof,
getMessagesAndProofsForL2Transaction,
......
......@@ -32,7 +32,7 @@
},
"devDependencies": {
"@discoveryjs/json-ext": "^0.5.3",
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/core-utils": "0.7.4",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abi": "^5.5.0",
"@ethersproject/bignumber": "^5.5.0",
......@@ -62,7 +62,7 @@
"ethers": "^5.4.5",
"lint-staged": "11.0.0",
"mocha": "^9.1.2",
"node-fetch": "2.6.5",
"node-fetch": "2.6.7",
"solc": "0.8.7-fixed",
"ts-mocha": "^8.0.0",
"ts-node": "^10.0.0"
......
......@@ -6,6 +6,7 @@ import {
POOL_INIT_CODE_HASH_OPTIMISM_KOVAN,
} from '@uniswap/v3-sdk'
import { Token } from '@uniswap/sdk-core'
import { UNISWAP_V3_FACTORY_ADDRESS } from './constants'
import { downloadAllSolcVersions } from './solc'
import {
......
......@@ -5,6 +5,7 @@ import {
POOL_INIT_CODE_HASH_OPTIMISM_KOVAN,
} from '@uniswap/v3-sdk'
import { sleep, add0x, remove0x, clone } from '@eth-optimism/core-utils'
import {
OLD_ETH_ADDRESS,
WETH_TRANSFER_ADDRESSES,
......
/* eslint @typescript-eslint/no-var-requires: "off" */
import fetch from 'node-fetch'
import path from 'path'
import fs from 'fs'
import path from 'path'
import fetch from 'node-fetch'
import { ethers } from 'ethers'
import { clone } from '@eth-optimism/core-utils'
import setupMethods from 'solc/wrapper'
import {
COMPILER_VERSIONS_TO_SOLC,
EMSCRIPTEN_BUILD_LIST,
......
import { ethers } from 'ethers'
import fs from 'fs'
import { ethers } from 'ethers'
import { add0x, remove0x, clone } from '@eth-optimism/core-utils'
import { StateDump, SurgeryDataSources, AccountType } from './types'
import { findAccount } from './utils'
import { handlers } from './handlers'
......
/* eslint @typescript-eslint/no-var-requires: "off" */
import { createReadStream } from 'fs'
import * as fs from 'fs'
import * as assert from 'assert'
import { ethers } from 'ethers'
import { abi as UNISWAP_FACTORY_ABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json'
import { Interface } from '@ethersproject/abi'
import { parseChunked } from '@discoveryjs/json-ext'
import { createReadStream } from 'fs'
import * as fs from 'fs'
import byline from 'byline'
import * as dotenv from 'dotenv'
import * as assert from 'assert'
import { reqenv, getenv, remove0x } from '@eth-optimism/core-utils'
import {
Account,
EtherscanContract,
......
import { KECCAK256_RLP_S, KECCAK256_NULL_S } from 'ethereumjs-util'
import { add0x } from '@eth-optimism/core-utils'
import { ethers } from 'ethers'
import { expect, env } from './setup'
import { AccountType } from '../scripts/types'
......
import { KECCAK256_RLP_S, KECCAK256_NULL_S } from 'ethereumjs-util'
import { add0x } from '@eth-optimism/core-utils'
import { expect, env } from './setup'
import { AccountType, Account } from '../scripts/types'
......
import { expect } from '@eth-optimism/core-utils/test/setup'
import { BigNumber } from 'ethers'
import { env } from './setup'
describe('erc20', () => {
......
import { ethers, BigNumber, Contract } from 'ethers'
import { expect, env, ERC20_ABI } from './setup'
import { AccountType } from '../scripts/types'
import { GenesisJsonProvider } from './provider'
import { AccountType } from '../scripts/types'
describe('predeploys', () => {
const predeploys = {
......
import { expect } from '@eth-optimism/core-utils/test/setup'
import { ethers, BigNumber } from 'ethers'
import { GenesisJsonProvider } from './provider'
import { Genesis } from '@eth-optimism/core-utils/src/types'
import {
remove0x,
......@@ -8,6 +7,8 @@ import {
} from '@eth-optimism/core-utils/src/common/hex-strings'
import { KECCAK256_RLP_S, KECCAK256_NULL_S } from 'ethereumjs-util'
import { GenesisJsonProvider } from './provider'
const account = '0x66a84544bed4ca45b3c024776812abf87728fbaf'
const genesis: Genesis = {
......
import path from 'path'
import { ethers } from 'ethers'
import { BigNumber } from '@ethersproject/bignumber'
import { Deferrable } from '@ethersproject/properties'
......@@ -16,8 +18,6 @@ import {
Listener,
} from '@ethersproject/abstract-provider'
import { KECCAK256_RLP_S, KECCAK256_NULL_S } from 'ethereumjs-util'
import path from 'path'
import { bytes32ify, remove0x, add0x } from '@eth-optimism/core-utils'
// Represents the ethereum state
......
......@@ -6,10 +6,11 @@ import * as dotenv from 'dotenv'
import { getenv, remove0x } from '@eth-optimism/core-utils'
import { providers, BigNumber } from 'ethers'
import { solidity } from 'ethereum-waffle'
import { GenesisJsonProvider } from './provider'
import { SurgeryDataSources, Account, AccountType } from '../scripts/types'
import { loadSurgeryData } from '../scripts/data'
import { classify, classifiers } from '../scripts/classifiers'
import { GenesisJsonProvider } from './provider'
// Chai plugins go here.
chai.use(chaiAsPromised)
......
import { ethers } from 'ethers'
import { abi as UNISWAP_POOL_ABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json'
import { expect, env, ERC20_ABI } from './setup'
import { UNISWAP_V3_NFPM_ADDRESS } from '../scripts/constants'
import { getUniswapV3Factory, replaceWETH } from '../scripts/utils'
import { expect, env, ERC20_ABI } from './setup'
import { AccountType } from '../scripts/types'
describe('uniswap contracts', () => {
......
import { expect } from '@eth-optimism/core-utils/test/setup'
import fs from 'fs/promises'
import path from 'path'
import { expect } from '@eth-optimism/core-utils/test/setup'
import { isBytecodeERC20 } from '../scripts/utils'
describe('Utils', () => {
......
# @eth-optimism/replica-healthcheck
## 0.3.4
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
## 0.3.3
### Patch Changes
......
{
"private": true,
"name": "@eth-optimism/replica-healthcheck",
"version": "0.3.3",
"version": "0.3.4",
"description": "[Optimism] Service for monitoring the health of replica nodes",
"main": "dist/index",
"types": "dist/index",
......@@ -33,7 +33,7 @@
},
"dependencies": {
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/core-utils": "0.7.4",
"dotenv": "^10.0.0",
"ethers": "^5.4.5",
"express": "^4.17.1",
......
import express from 'express'
import { Server } from 'net'
import express from 'express'
import promBundle from 'express-prom-bundle'
import { Gauge, Histogram } from 'prom-client'
import cron from 'node-cron'
......
# @eth-optimism/sdk
## 0.0.5
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
- @eth-optimism/contracts@0.5.9
## 0.0.4
### Patch Changes
......
{
"name": "@eth-optimism/sdk",
"version": "0.0.4",
"version": "0.0.5",
"description": "[Optimism] Tools for working with Optimism",
"main": "dist/index",
"types": "dist/index",
......@@ -59,8 +59,8 @@
"typescript": "^4.3.5"
},
"dependencies": {
"@eth-optimism/contracts": "0.5.8",
"@eth-optimism/core-utils": "0.7.3",
"@eth-optimism/contracts": "0.5.9",
"@eth-optimism/core-utils": "0.7.4",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abstract-signer": "^5.5.0",
"ethers": "^5.5.2"
......
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Overrides, Signer, BigNumber } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import {
CrossChainMessageRequest,
ICrossChainMessenger,
ICrossChainProvider,
L1ToL2Overrides,
MessageLike,
NumberLike,
MessageDirection,
} from './interfaces'
import { omit } from './utils'
export class CrossChainMessenger implements ICrossChainMessenger {
provider: ICrossChainProvider
l1Signer: Signer
l2Signer: Signer
/**
* Creates a new CrossChainMessenger instance.
*
* @param opts Options for the messenger.
* @param opts.provider CrossChainProvider to use to send messages.
* @param opts.l1Signer Signer to use to send messages on L1.
* @param opts.l2Signer Signer to use to send messages on L2.
*/
constructor(opts: {
provider: ICrossChainProvider
l1Signer: Signer
l2Signer: Signer
}) {
this.provider = opts.provider
this.l1Signer = opts.l1Signer
this.l2Signer = opts.l2Signer
}
public async sendMessage(
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse> {
const tx = await this.populateTransaction.sendMessage(message, overrides)
if (message.direction === MessageDirection.L1_TO_L2) {
return this.l1Signer.sendTransaction(tx)
} else {
return this.l2Signer.sendTransaction(tx)
}
}
public async resendMessage(
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<TransactionResponse> {
const tx = await this.populateTransaction.resendMessage(
message,
messageGasLimit,
overrides
)
return this.l1Signer.sendTransaction(tx)
}
public async finalizeMessage(
message: MessageLike,
overrides?: Overrides
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
public async depositETH(
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
public async withdrawETH(
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
populateTransaction = {
sendMessage: async (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
): Promise<TransactionRequest> => {
if (message.direction === MessageDirection.L1_TO_L2) {
return this.provider.contracts.l1.L1CrossDomainMessenger.connect(
this.l1Signer
).populateTransaction.sendMessage(
message.target,
message.message,
overrides?.l2GasLimit ||
(await this.provider.estimateL2MessageGasLimit(message)),
omit(overrides || {}, 'l2GasLimit')
)
} else {
return this.provider.contracts.l2.L2CrossDomainMessenger.connect(
this.l2Signer
).populateTransaction.sendMessage(
message.target,
message.message,
0, // Gas limit goes unused when sending from L2 to L1
omit(overrides || {}, 'l2GasLimit')
)
}
},
resendMessage: async (
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest> => {
const resolved = await this.provider.toCrossChainMessage(message)
if (resolved.direction === MessageDirection.L2_TO_L1) {
throw new Error(`cannot resend L2 to L1 message`)
}
return this.provider.contracts.l1.L1CrossDomainMessenger.connect(
this.l1Signer
).populateTransaction.replayMessage(
resolved.target,
resolved.sender,
resolved.message,
resolved.messageNonce,
resolved.gasLimit,
messageGasLimit,
overrides || {}
)
},
finalizeMessage: async (
message: MessageLike,
overrides?: Overrides
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
depositETH: async (
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
withdrawETH: async (
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
}
estimateGas = {
sendMessage: async (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
): Promise<BigNumber> => {
const tx = await this.populateTransaction.sendMessage(message, overrides)
if (message.direction === MessageDirection.L1_TO_L2) {
return this.provider.l1Provider.estimateGas(tx)
} else {
return this.provider.l2Provider.estimateGas(tx)
}
},
resendMessage: async (
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<BigNumber> => {
const tx = await this.populateTransaction.resendMessage(
message,
messageGasLimit,
overrides
)
return this.provider.l1Provider.estimateGas(tx)
},
finalizeMessage: async (
message: MessageLike,
overrides?: Overrides
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
depositETH: async (
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
withdrawETH: async (
amount: NumberLike,
overrides?: Overrides
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
}
}
......@@ -6,16 +6,19 @@ import {
} from '@ethersproject/abstract-provider'
import { ethers, BigNumber, Event } from 'ethers'
import { sleep } from '@eth-optimism/core-utils'
import {
ICrossChainProvider,
OEContracts,
OEContractsLike,
MessageLike,
MessageRequestLike,
TransactionLike,
AddressLike,
NumberLike,
ProviderLike,
CrossChainMessage,
CrossChainMessageRequest,
MessageDirection,
MessageStatus,
TokenBridgeMessage,
......@@ -23,6 +26,8 @@ import {
MessageReceiptStatus,
CustomBridges,
CustomBridgesLike,
StateRoot,
StateRootBatch,
} from './interfaces'
import {
toProvider,
......@@ -49,6 +54,7 @@ export class CrossChainProvider implements ICrossChainProvider {
* @param opts.l2Provider Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.contracts Optional contract address overrides.
* @param opts.bridges Optional bridge address list.
*/
constructor(opts: {
l1Provider: ProviderLike
......@@ -128,6 +134,7 @@ export class CrossChainProvider implements ICrossChainProvider {
sender: parsed.args.sender,
message: parsed.args.message,
messageNonce: parsed.args.messageNonce,
gasLimit: parsed.args.gasLimit,
logIndex: log.logIndex,
blockNumber: log.blockNumber,
transactionHash: log.transactionHash,
......@@ -335,7 +342,44 @@ export class CrossChainProvider implements ICrossChainProvider {
}
public async getMessageStatus(message: MessageLike): Promise<MessageStatus> {
throw new Error('Not implemented')
const resolved = await this.toCrossChainMessage(message)
const receipt = await this.getMessageReceipt(resolved)
if (resolved.direction === MessageDirection.L1_TO_L2) {
if (receipt === null) {
return MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE
} else {
if (receipt.receiptStatus === MessageReceiptStatus.RELAYED_SUCCEEDED) {
return MessageStatus.RELAYED
} else {
return MessageStatus.FAILED_L1_TO_L2_MESSAGE
}
}
} else {
if (receipt === null) {
const stateRoot = await this.getMessageStateRoot(resolved)
if (stateRoot === null) {
return MessageStatus.STATE_ROOT_NOT_PUBLISHED
} else {
const challengePeriod = await this.getChallengePeriodSeconds()
const targetBlock = await this.l1Provider.getBlock(
stateRoot.blockNumber
)
const latestBlock = await this.l1Provider.getBlock('latest')
if (targetBlock.timestamp + challengePeriod > latestBlock.timestamp) {
return MessageStatus.IN_CHALLENGE_PERIOD
} else {
return MessageStatus.READY_FOR_RELAY
}
}
} else {
if (receipt.receiptStatus === MessageReceiptStatus.RELAYED_SUCCEEDED) {
return MessageStatus.RELAYED
} else {
return MessageStatus.READY_FOR_RELAY
}
}
}
}
public async getMessageReceipt(
......@@ -420,9 +464,36 @@ export class CrossChainProvider implements ICrossChainProvider {
}
public async estimateL2MessageGasLimit(
message: MessageLike
message: MessageRequestLike,
opts?: {
bufferPercent?: number
from?: string
}
): Promise<BigNumber> {
throw new Error('Not implemented')
let resolved: CrossChainMessage | CrossChainMessageRequest
let from: string
if ((message as CrossChainMessage).messageNonce === undefined) {
resolved = message as CrossChainMessageRequest
from = opts?.from
} else {
resolved = await this.toCrossChainMessage(message as MessageLike)
from = opts?.from || (resolved as CrossChainMessage).sender
}
// L2 message gas estimation is only used for L1 => L2 messages.
if (resolved.direction === MessageDirection.L2_TO_L1) {
throw new Error(`cannot estimate gas limit for L2 => L1 message`)
}
const estimate = await this.l2Provider.estimateGas({
from,
to: resolved.target,
data: resolved.message,
})
// Return the estimate plus a buffer of 20% just in case.
const bufferPercent = opts?.bufferPercent || 20
return estimate.mul(100 + bufferPercent).div(100)
}
public async estimateMessageWaitTimeSeconds(
......@@ -431,9 +502,162 @@ export class CrossChainProvider implements ICrossChainProvider {
throw new Error('Not implemented')
}
public async estimateMessageWaitTimeBlocks(
public async getChallengePeriodSeconds(): Promise<number> {
const challengePeriod =
await this.contracts.l1.StateCommitmentChain.FRAUD_PROOF_WINDOW()
return challengePeriod.toNumber()
}
public async getMessageStateRoot(
message: MessageLike
): Promise<number> {
throw new Error('Not implemented')
): Promise<StateRoot | null> {
const resolved = await this.toCrossChainMessage(message)
// State roots are only a thing for L2 to L1 messages.
if (resolved.direction === MessageDirection.L1_TO_L2) {
throw new Error(`cannot get a state root for an L1 to L2 message`)
}
// We need the block number of the transaction that triggered the message so we can look up the
// state root batch that corresponds to that block number.
const messageTxReceipt = await this.l2Provider.getTransactionReceipt(
resolved.transactionHash
)
// Every block has exactly one transaction in it. Since there's a genesis block, the
// transaction index will always be one less than the block number.
const messageTxIndex = messageTxReceipt.blockNumber - 1
// Pull down the state root batch, we'll try to pick out the specific state root that
// corresponds to our message.
const stateRootBatch = await this.getStateRootBatchByTransactionIndex(
messageTxIndex
)
// No state root batch, no state root.
if (stateRootBatch === null) {
return null
}
// We have a state root batch, now we need to find the specific state root for our transaction.
// First we need to figure out the index of the state root within the batch we found. This is
// going to be the original transaction index offset by the total number of previous state
// roots.
const indexInBatch =
messageTxIndex - stateRootBatch.header.prevTotalElements.toNumber()
// Just a sanity check.
if (stateRootBatch.stateRoots.length <= indexInBatch) {
// Should never happen!
throw new Error(`state root does not exist in batch`)
}
return {
blockNumber: stateRootBatch.blockNumber,
header: stateRootBatch.header,
stateRoot: stateRootBatch.stateRoots[indexInBatch],
}
}
public async getStateBatchAppendedEventByBatchIndex(
batchIndex: number
): Promise<ethers.Event | null> {
const events = await this.contracts.l1.StateCommitmentChain.queryFilter(
this.contracts.l1.StateCommitmentChain.filters.StateBatchAppended(
batchIndex
)
)
if (events.length === 0) {
return null
} else if (events.length > 1) {
// Should never happen!
throw new Error(`found more than one StateBatchAppended event`)
} else {
return events[0]
}
}
public async getStateBatchAppendedEventByTransactionIndex(
transactionIndex: number
): Promise<ethers.Event | null> {
const isEventHi = (event: ethers.Event, index: number) => {
const prevTotalElements = event.args._prevTotalElements.toNumber()
return index < prevTotalElements
}
const isEventLo = (event: ethers.Event, index: number) => {
const prevTotalElements = event.args._prevTotalElements.toNumber()
const batchSize = event.args._batchSize.toNumber()
return index >= prevTotalElements + batchSize
}
const totalBatches: ethers.BigNumber =
await this.contracts.l1.StateCommitmentChain.getTotalBatches()
if (totalBatches.eq(0)) {
return null
}
let lowerBound = 0
let upperBound = totalBatches.toNumber() - 1
let batchEvent: ethers.Event | null =
await this.getStateBatchAppendedEventByBatchIndex(upperBound)
if (isEventLo(batchEvent, transactionIndex)) {
// Upper bound is too low, means this transaction doesn't have a corresponding state batch yet.
return null
} else if (!isEventHi(batchEvent, transactionIndex)) {
// Upper bound is not too low and also not too high. This means the upper bound event is the
// one we're looking for! Return it.
return batchEvent
}
// Binary search to find the right event. The above checks will guarantee that the event does
// exist and that we'll find it during this search.
while (lowerBound < upperBound) {
const middleOfBounds = Math.floor((lowerBound + upperBound) / 2)
batchEvent = await this.getStateBatchAppendedEventByBatchIndex(
middleOfBounds
)
if (isEventHi(batchEvent, transactionIndex)) {
upperBound = middleOfBounds
} else if (isEventLo(batchEvent, transactionIndex)) {
lowerBound = middleOfBounds
} else {
break
}
}
return batchEvent
}
public async getStateRootBatchByTransactionIndex(
transactionIndex: number
): Promise<StateRootBatch | null> {
const stateBatchAppendedEvent =
await this.getStateBatchAppendedEventByTransactionIndex(transactionIndex)
if (stateBatchAppendedEvent === null) {
return null
}
const stateBatchTransaction = await stateBatchAppendedEvent.getTransaction()
const [stateRoots] =
this.contracts.l1.StateCommitmentChain.interface.decodeFunctionData(
'appendStateBatch',
stateBatchTransaction.data
)
return {
blockNumber: stateBatchAppendedEvent.blockNumber,
stateRoots,
header: {
batchIndex: stateBatchAppendedEvent.args._batchIndex,
batchRoot: stateBatchAppendedEvent.args._batchRoot,
batchSize: stateBatchAppendedEvent.args._batchSize,
prevTotalElements: stateBatchAppendedEvent.args._prevTotalElements,
extraData: stateBatchAppendedEvent.args._extraData,
},
}
}
}
export * from './interfaces'
export * from './utils'
export * from './cross-chain-provider'
export * from './cross-chain-messenger'
......@@ -3,6 +3,7 @@ import {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { NumberLike, L1ToL2Overrides } from './types'
import { ICrossChainMessenger } from './cross-chain-messenger'
......
import { Overrides, Signer } from 'ethers'
import { Overrides, Signer, BigNumber } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import {
MessageLike,
NumberLike,
......@@ -21,9 +22,14 @@ export interface ICrossChainMessenger {
provider: ICrossChainProvider
/**
* Signer that will carry out L1/L2 transactions.
* Signer that will carry out L1 transactions.
*/
l1Signer: Signer
/**
* Signer that will carry out L2 transactions.
*/
signer: Signer
l2Signer: Signer
/**
* Sends a given cross chain message. Where the message is sent depends on the direction attached
......@@ -106,7 +112,7 @@ export interface ICrossChainMessenger {
sendMessage: (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
) => Promise<TransactionResponse>
) => Promise<TransactionRequest>
/**
* Generates a transaction that resends a given cross chain message. Only applies to L1 to L2
......@@ -177,7 +183,7 @@ export interface ICrossChainMessenger {
sendMessage: (
message: CrossChainMessageRequest,
overrides?: L1ToL2Overrides
) => Promise<TransactionResponse>
) => Promise<BigNumber>
/**
* Estimates gas required to resend a cross chain message. Only applies to L1 to L2 messages.
......@@ -191,7 +197,7 @@ export interface ICrossChainMessenger {
message: MessageLike,
messageGasLimit: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
): Promise<BigNumber>
/**
* Estimates gas required to finalize a cross chain message. Only applies to L2 to L1 messages.
......@@ -203,7 +209,7 @@ export interface ICrossChainMessenger {
finalizeMessage(
message: MessageLike,
overrides?: Overrides
): Promise<TransactionRequest>
): Promise<BigNumber>
/**
* Estimates gas required to deposit some ETH into the L2 chain.
......@@ -215,7 +221,7 @@ export interface ICrossChainMessenger {
depositETH(
amount: NumberLike,
overrides?: L1ToL2Overrides
): Promise<TransactionRequest>
): Promise<BigNumber>
/**
* Estimates gas required to withdraw some ETH back to the L1 chain.
......@@ -224,9 +230,6 @@ export interface ICrossChainMessenger {
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawETH(
amount: NumberLike,
overrides?: Overrides
): Promise<TransactionRequest>
withdrawETH(amount: NumberLike, overrides?: Overrides): Promise<BigNumber>
}
}
import { BigNumber } from 'ethers'
import { Event, BigNumber } from 'ethers'
import { Provider, BlockTag } from '@ethersproject/abstract-provider'
import {
MessageLike,
MessageRequestLike,
TransactionLike,
AddressLike,
NumberLike,
......@@ -12,6 +14,8 @@ import {
OEContracts,
MessageReceipt,
CustomBridges,
StateRoot,
StateRootBatch,
} from './types'
/**
......@@ -200,9 +204,18 @@ export interface ICrossChainProvider {
* L1 => L2 messages. You would supply this gas limit when sending the message to L2.
*
* @param message Message get a gas estimate for.
* @param opts Options object.
* @param opts.bufferPercent Percentage of gas to add to the estimate. Defaults to 20.
* @param opts.from Address to use as the sender.
* @returns Estimates L2 gas limit.
*/
estimateL2MessageGasLimit(message: MessageLike): Promise<BigNumber>
estimateL2MessageGasLimit(
message: MessageRequestLike,
opts?: {
bufferPercent?: number
from?: string
}
): Promise<BigNumber>
/**
* Returns the estimated amount of time before the message can be executed. When this is a
......@@ -216,13 +229,52 @@ export interface ICrossChainProvider {
estimateMessageWaitTimeSeconds(message: MessageLike): Promise<number>
/**
* Returns the estimated amount of time before the message can be executed (in L1 blocks).
* When this is a message being sent to L1, this will return the estimated time until the message
* will complete its challenge period. When this is a message being sent to L2, this will return
* the estimated amount of time until the message will be picked up and executed on L2.
* Queries the current challenge period in seconds from the StateCommitmentChain.
*
* @param message Message to estimate the time remaining for.
* @returns Estimated amount of time remaining (in blocks) before the message can be executed.
* @returns Current challenge period in seconds.
*/
getChallengePeriodSeconds(): Promise<number>
/**
* Returns the state root that corresponds to a given message. This is the state root for the
* block in which the transaction was included, as published to the StateCommitmentChain. If the
* state root for the given message has not been published yet, this function returns null.
*
* @param message Message to find a state root for.
* @returns State root for the block in which the message was created.
*/
getMessageStateRoot(message: MessageLike): Promise<StateRoot | null>
/**
* Returns the StateBatchAppended event that was emitted when the batch with a given index was
* created. Returns null if no such event exists (the batch has not been submitted).
*
* @param batchIndex Index of the batch to find an event for.
* @returns StateBatchAppended event for the batch, or null if no such batch exists.
*/
getStateBatchAppendedEventByBatchIndex(
batchIndex: number
): Promise<Event | null>
/**
* Returns the StateBatchAppended event for the batch that includes the transaction with the
* given index. Returns null if no such event exists.
*
* @param transactionIndex Index of the L2 transaction to find an event for.
* @returns StateBatchAppended event for the batch that includes the given transaction by index.
*/
getStateBatchAppendedEventByTransactionIndex(
transactionIndex: number
): Promise<Event | null>
/**
* Returns information about the state root batch that included the state root for the given
* transaction by index. Returns null if no such state root has been published yet.
*
* @param transactionIndex Index of the L2 transaction to find a state root batch for.
* @returns State root batch for the given transaction index, or null if none exists yet.
*/
estimateMessageWaitTimeBlocks(message: MessageLike): Promise<number>
getStateRootBatchByTransactionIndex(
transactionIndex: number
): Promise<StateRootBatch | null>
}
......@@ -143,7 +143,6 @@ export interface CrossChainMessageRequest {
direction: MessageDirection
target: string
message: string
l2GasLimit: NumberLike
}
/**
......@@ -162,6 +161,7 @@ export interface CoreCrossChainMessage {
*/
export interface CrossChainMessage extends CoreCrossChainMessage {
direction: MessageDirection
gasLimit: number
logIndex: number
blockNumber: number
transactionHash: string
......@@ -212,9 +212,19 @@ export interface StateRootBatchHeader {
}
/**
* State root batch, including header and actual state roots.
* Information about a state root, including header, block number, and root iself.
*/
export interface StateRoot {
blockNumber: number
header: StateRootBatchHeader
stateRoot: string
}
/**
* Information about a batch of state roots.
*/
export interface StateRootBatch {
blockNumber: number
header: StateRootBatchHeader
stateRoots: string[]
}
......@@ -225,7 +235,7 @@ export interface StateRootBatch {
* limit field (gas used depends on the amount of gas provided).
*/
export type L1ToL2Overrides = Overrides & {
l2GasLimit: NumberLike
l2GasLimit?: NumberLike
}
/**
......@@ -234,13 +244,22 @@ export type L1ToL2Overrides = Overrides & {
export type TransactionLike = string | TransactionReceipt | TransactionResponse
/**
* Stuff that can be coerced into a message.
* Stuff that can be coerced into a CrossChainMessage.
*/
export type MessageLike =
| CrossChainMessage
| TransactionLike
| TokenBridgeMessage
/**
* Stuff that can be coerced into a CrossChainMessageRequest.
*/
export type MessageRequestLike =
| CrossChainMessageRequest
| CrossChainMessage
| TransactionLike
| TokenBridgeMessage
/**
* Stuff that can be coerced into a provider.
*/
......
import assert from 'assert'
import {
Provider,
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { ethers, BigNumber } from 'ethers'
import {
ProviderLike,
TransactionLike,
......
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { ethers, Contract } from 'ethers'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
import {
OEContracts,
OEL1Contracts,
......@@ -10,8 +13,6 @@ import {
CustomBridges,
CustomBridgesLike,
} from '../interfaces'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
/**
* Full list of default L2 contract addresses.
......
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { CoreCrossChainMessage } from '../interfaces'
/**
......
......@@ -7,13 +7,40 @@ contract MockMessenger is ICrossDomainMessenger {
return address(0);
}
uint256 public nonce;
// Empty function to satisfy the interface.
function sendMessage(
address _target,
bytes calldata _message,
uint32 _gasLimit
) public {
return;
emit SentMessage(
_target,
msg.sender,
_message,
nonce,
_gasLimit
);
nonce++;
}
function replayMessage(
address _target,
address _sender,
bytes calldata _message,
uint256 _queueIndex,
uint32 _oldGasLimit,
uint32 _newGasLimit
) public {
emit SentMessage(
_target,
_sender,
_message,
nonce,
_newGasLimit
);
nonce++;
}
struct SentMessageEventParams {
......
pragma solidity ^0.8.9;
contract MockSCC {
event StateBatchAppended(
uint256 indexed _batchIndex,
bytes32 _batchRoot,
uint256 _batchSize,
uint256 _prevTotalElements,
bytes _extraData
);
struct StateBatchAppendedArgs {
uint256 batchIndex;
bytes32 batchRoot;
uint256 batchSize;
uint256 prevTotalElements;
bytes extraData;
}
// Window in seconds, will resolve to 100 blocks.
uint256 public FRAUD_PROOF_WINDOW = 1500;
uint256 public batches = 0;
StateBatchAppendedArgs public sbaParams;
function getTotalBatches() public view returns (uint256) {
return batches;
}
function setSBAParams(
StateBatchAppendedArgs memory _args
) public {
sbaParams = _args;
}
function appendStateBatch(
bytes32[] memory _roots,
uint256 _shouldStartAtIndex
) public {
batches++;
emit StateBatchAppended(
sbaParams.batchIndex,
sbaParams.batchRoot,
sbaParams.batchSize,
sbaParams.prevTotalElements,
sbaParams.extraData
);
}
}
import './setup'
import { Contract } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from './setup'
import {
CrossChainProvider,
CrossChainMessenger,
MessageDirection,
} from '../src'
describe('CrossChainMessenger', () => {
let l1Signer: any
let l2Signer: any
before(async () => {
;[l1Signer, l2Signer] = await ethers.getSigners()
})
describe('sendMessage', () => {
describe('when no l2GasLimit is provided', () => {
it('should send a message with an estimated l2GasLimit')
let l1Messenger: Contract
let l2Messenger: Contract
let provider: CrossChainProvider
let messenger: CrossChainMessenger
beforeEach(async () => {
l1Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
l2Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
contracts: {
l1: {
L1CrossDomainMessenger: l1Messenger.address,
},
l2: {
L2CrossDomainMessenger: l2Messenger.address,
},
},
})
messenger = new CrossChainMessenger({
provider,
l1Signer,
l2Signer,
})
})
describe('when an l2GasLimit is provided', () => {
it('should send a message with the provided l2GasLimit')
describe('when the message is an L1 to L2 message', () => {
describe('when no l2GasLimit is provided', () => {
it('should send a message with an estimated l2GasLimit', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
const estimate = await provider.estimateL2MessageGasLimit(message)
await expect(messenger.sendMessage(message))
.to.emit(l1Messenger, 'SentMessage')
.withArgs(
message.target,
await l1Signer.getAddress(),
message.message,
0,
estimate
)
})
})
describe('when an l2GasLimit is provided', () => {
it('should send a message with the provided l2GasLimit', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
await expect(
messenger.sendMessage(message, {
l2GasLimit: 1234,
})
)
.to.emit(l1Messenger, 'SentMessage')
.withArgs(
message.target,
await l1Signer.getAddress(),
message.message,
0,
1234
)
})
})
})
describe('when the message is an L2 to L1 message', () => {
it('should send a message', async () => {
const message = {
direction: MessageDirection.L2_TO_L1,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
await expect(messenger.sendMessage(message))
.to.emit(l2Messenger, 'SentMessage')
.withArgs(
message.target,
await l2Signer.getAddress(),
message.message,
0,
0
)
})
})
})
describe('resendMessage', () => {
describe('when the message being resent exists', () => {
it('should resend the message with the new gas limit')
let l1Messenger: Contract
let l2Messenger: Contract
let provider: CrossChainProvider
let messenger: CrossChainMessenger
beforeEach(async () => {
l1Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
l2Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
contracts: {
l1: {
L1CrossDomainMessenger: l1Messenger.address,
},
l2: {
L2CrossDomainMessenger: l2Messenger.address,
},
},
})
messenger = new CrossChainMessenger({
provider,
l1Signer,
l2Signer,
})
})
describe('when the message being resent does not exist', () => {
it('should throw an error')
describe('when resending an L1 to L2 message', () => {
it('should resend the message with the new gas limit', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
const sent = await messenger.sendMessage(message, {
l2GasLimit: 1234,
})
await expect(messenger.resendMessage(sent, 10000))
.to.emit(l1Messenger, 'SentMessage')
.withArgs(
message.target,
await l1Signer.getAddress(),
message.message,
1, // nonce is now 1
10000
)
})
})
describe('when resending an L2 to L1 message', () => {
it('should throw an error', async () => {
const message = {
direction: MessageDirection.L2_TO_L1,
target: '0x' + '11'.repeat(20),
message: '0x' + '22'.repeat(32),
}
const sent = await messenger.sendMessage(message, {
l2GasLimit: 1234,
})
await expect(messenger.resendMessage(sent, 10000)).to.be.rejected
})
})
})
......
import { expect } from './setup'
import { Provider } from '@ethersproject/abstract-provider'
import { expectApprox } from '@eth-optimism/core-utils'
import { Contract } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from './setup'
import {
CrossChainProvider,
MessageDirection,
CONTRACT_ADDRESSES,
hashCrossChainMessage,
omit,
MessageStatus,
CrossChainMessage,
} from '../src'
import { DUMMY_MESSAGE } from './helpers'
describe('CrossChainProvider', () => {
describe('construction', () => {
......@@ -250,13 +255,7 @@ describe('CrossChainProvider', () => {
for (const n of [1, 2, 4, 8]) {
it(`should find ${n} messages when the transaction emits ${n} messages`, async () => {
const messages = [...Array(n)].map(() => {
return {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
return DUMMY_MESSAGE
})
const tx = await l1Messenger.triggerSentMessageEvents(messages)
......@@ -271,6 +270,7 @@ describe('CrossChainProvider', () => {
target: message.target,
message: message.message,
messageNonce: ethers.BigNumber.from(message.messageNonce),
gasLimit: ethers.BigNumber.from(message.gasLimit),
logIndex: i,
blockNumber: tx.blockNumber,
transactionHash: tx.hash,
......@@ -309,13 +309,7 @@ describe('CrossChainProvider', () => {
for (const n of [1, 2, 4, 8]) {
it(`should find ${n} messages when the transaction emits ${n} messages`, async () => {
const messages = [...Array(n)].map(() => {
return {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
return DUMMY_MESSAGE
})
const tx = await l1Messenger.triggerSentMessageEvents(messages)
......@@ -328,6 +322,7 @@ describe('CrossChainProvider', () => {
target: message.target,
message: message.message,
messageNonce: ethers.BigNumber.from(message.messageNonce),
gasLimit: ethers.BigNumber.from(message.gasLimit),
logIndex: i,
blockNumber: tx.blockNumber,
transactionHash: tx.hash,
......@@ -692,6 +687,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -736,15 +732,7 @@ describe('CrossChainProvider', () => {
describe('when the input is a TransactionLike', () => {
describe('when the transaction sent exactly one message', () => {
it('should return the CrossChainMessage sent in the transaction', async () => {
const message = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
const tx = await l1Messenger.triggerSentMessageEvents([message])
const tx = await l1Messenger.triggerSentMessageEvents([DUMMY_MESSAGE])
const foundCrossChainMessages =
await provider.getMessagesByTransaction(tx)
const resolved = await provider.toCrossChainMessage(tx)
......@@ -755,13 +743,7 @@ describe('CrossChainProvider', () => {
describe('when the transaction sent more than one message', () => {
it('should throw an error', async () => {
const messages = [...Array(2)].map(() => {
return {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
return DUMMY_MESSAGE
})
const tx = await l1Messenger.triggerSentMessageEvents(messages)
......@@ -783,45 +765,200 @@ describe('CrossChainProvider', () => {
})
describe('getMessageStatus', () => {
let scc: Contract
let l1Messenger: Contract
let l2Messenger: Contract
let provider: CrossChainProvider
beforeEach(async () => {
// TODO: Get rid of the nested awaits here. Could be a good first issue for someone.
scc = (await (await ethers.getContractFactory('MockSCC')).deploy()) as any
l1Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
l2Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
contracts: {
l1: {
L1CrossDomainMessenger: l1Messenger.address,
StateCommitmentChain: scc.address,
},
l2: {
L2CrossDomainMessenger: l2Messenger.address,
},
},
})
})
const sendAndGetDummyMessage = async (direction: MessageDirection) => {
const messenger =
direction === MessageDirection.L1_TO_L2 ? l1Messenger : l2Messenger
const tx = await messenger.triggerSentMessageEvents([DUMMY_MESSAGE])
return (
await provider.getMessagesByTransaction(tx, {
direction,
})
)[0]
}
const submitStateRootBatchForMessage = async (
message: CrossChainMessage
) => {
await scc.setSBAParams({
batchIndex: 0,
batchRoot: ethers.constants.HashZero,
batchSize: 1,
prevTotalElements: message.blockNumber,
extraData: '0x',
})
await scc.appendStateBatch([ethers.constants.HashZero], 0)
}
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE')
it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L1_TO_L2
)
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE
)
})
})
describe('when the message has been executed on L2', () => {
it('should return a status of RELAYED')
it('should return a status of RELAYED', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L1_TO_L2
)
await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.RELAYED
)
})
})
describe('when the message has been executed but failed', () => {
it('should return a status of FAILED_L1_TO_L2_MESSAGE')
it('should return a status of FAILED_L1_TO_L2_MESSAGE', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L1_TO_L2
)
await l2Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.FAILED_L1_TO_L2_MESSAGE
)
})
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the message state root has not been published', () => {
it('should return a status of STATE_ROOT_NOT_PUBLISHED')
it('should return a status of STATE_ROOT_NOT_PUBLISHED', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.STATE_ROOT_NOT_PUBLISHED
)
})
})
describe('when the message state root is still in the challenge period', () => {
it('should return a status of IN_CHALLENGE_PERIOD')
it('should return a status of IN_CHALLENGE_PERIOD', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.IN_CHALLENGE_PERIOD
)
})
})
describe('when the message is no longer in the challenge period', () => {
describe('when the message has been relayed successfully', () => {
it('should return a status of RELAYED')
it('should return a status of RELAYED', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
const challengePeriod = await provider.getChallengePeriodSeconds()
ethers.provider.send('evm_increaseTime', [challengePeriod + 1])
ethers.provider.send('evm_mine', [])
await l1Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.RELAYED
)
})
})
describe('when the message has been relayed but the relay failed', () => {
it('should return a status of READY_FOR_RELAY')
it('should return a status of READY_FOR_RELAY', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
const challengePeriod = await provider.getChallengePeriodSeconds()
ethers.provider.send('evm_increaseTime', [challengePeriod + 1])
ethers.provider.send('evm_mine', [])
await l1Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.READY_FOR_RELAY
)
})
})
describe('when the message has not been relayed', () => {
it('should return a status of READY_FOR_RELAY')
it('should return a status of READY_FOR_RELAY', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
const challengePeriod = await provider.getChallengePeriodSeconds()
ethers.provider.send('evm_increaseTime', [challengePeriod + 1])
ethers.provider.send('evm_mine', [])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.READY_FOR_RELAY
)
})
})
})
})
describe('when the message does not exist', () => {
// TODO: Figure out if this is the correct behavior. Mark suggests perhaps returning null.
it('should throw an error')
})
})
......@@ -872,6 +1009,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -902,6 +1040,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -932,6 +1071,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -967,6 +1107,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -1011,6 +1152,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -1042,6 +1184,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -1074,6 +1217,7 @@ describe('CrossChainProvider', () => {
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
......@@ -1090,7 +1234,93 @@ describe('CrossChainProvider', () => {
})
describe('estimateL2MessageGasLimit', () => {
it('should perform a gas estimation of the L2 action')
let provider: CrossChainProvider
beforeEach(async () => {
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
})
})
describe('when the message is an L1 to L2 message', () => {
it('should return an accurate gas estimate plus a ~20% buffer', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
const estimate = await ethers.provider.estimateGas({
to: message.target,
from: message.sender,
data: message.message,
})
// Approximately 20% greater than the estimate, +/- 1%.
expectApprox(
await provider.estimateL2MessageGasLimit(message),
estimate.mul(120).div(100),
{
percentUpperDeviation: 1,
percentLowerDeviation: 1,
}
)
})
it('should return an accurate gas estimate when a custom buffer is provided', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
const estimate = await ethers.provider.estimateGas({
to: message.target,
from: message.sender,
data: message.message,
})
// Approximately 30% greater than the estimate, +/- 1%.
expectApprox(
await provider.estimateL2MessageGasLimit(message, {
bufferPercent: 30,
}),
estimate.mul(130).div(100),
{
percentUpperDeviation: 1,
percentLowerDeviation: 1,
}
)
})
})
describe('when the message is an L2 to L1 message', () => {
it('should throw an error', async () => {
const message = {
direction: MessageDirection.L2_TO_L1,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
await expect(provider.estimateL2MessageGasLimit(message)).to.be.rejected
})
})
})
describe('estimateMessageWaitTimeBlocks', () => {
......
export const DUMMY_MESSAGE = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
export * from './constants'
import { expect } from '../setup'
import { Provider } from '@ethersproject/abstract-provider'
import { Contract } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from '../setup'
import { toProvider, toTransactionHash } from '../../src'
describe('type coercion utils', () => {
......
/* eslint-disable @typescript-eslint/no-empty-function */
import { Signer } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from '../setup'
import {
getOEContract,
......@@ -6,8 +9,6 @@ import {
CONTRACT_ADDRESSES,
DEFAULT_L2_CONTRACT_ADDRESSES,
} from '../../src'
import { Signer } from 'ethers'
import { ethers } from 'hardhat'
describe('contract connection utils', () => {
let signers: Signer[]
......
import { expect } from '../setup'
import { Contract, Signer } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import { expect } from '../setup'
import {
CoreCrossChainMessage,
encodeCrossChainMessage,
......
......@@ -11523,15 +11523,15 @@ node-fetch@*:
data-uri-to-buffer "^3.0.1"
fetch-blob "^3.1.2"
node-fetch@2.6.1, node-fetch@^2.6.0, node-fetch@^2.6.1:
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@2.6.5:
version "2.6.5"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
node-fetch@2.6.7, node-fetch@^2.6.0, node-fetch@^2.6.1:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
......
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