Commit a05ceb1d authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

Merge pull request #1952 from ethereum-optimism/develop

Develop -> Master PR
parents 88eb324e 8a3e400f
---
'@eth-optimism/batch-submitter': patch
---
Fix the numTxPerBatch metric
---
'@eth-optimism/message-relayer': patch
---
Removed old node.js service running script
---
'@eth-optimism/l2geth-exporter': patch
---
Initial build
---
'@eth-optimism/batch-submitter-service': patch
---
Fix metrics server
version: 2.1
orbs:
gcp-gke: circleci/gcp-gke@1.3.0
commands:
build-dockerfile:
parameters:
......@@ -39,6 +41,13 @@ jobs:
image-name: batch-submitter
target: batch-submitter
dockerfile: ./ops/docker/Dockerfile.packages
build-go-batch-submitter:
docker:
- image: cimg/base:2021.04
steps:
- build-dockerfile:
image-name: go-batch-submitter
dockerfile: ./ops/docker/Dockerfile.batch-submitter-service
build-deployer:
docker:
- image: cimg/base:2021.04
......@@ -61,6 +70,37 @@ jobs:
- build-dockerfile:
image-name: gas-oracle
dockerfile: ./ops/docker/Dockerfile.gas-oracle
build-integration-tests:
docker:
- image: cimg/base:2021.04
steps:
- build-dockerfile:
image-name: integration-tests
target: integration-tests
dockerfile: ./ops/docker/Dockerfile.packages
deploy-nightly:
docker:
- image: cimg/base:2021.04
steps:
- gcp-gke/install
- gcp-gke/update-kubeconfig-with-credentials:
cluster: $STACKMAN_CLUSTER
gcloud-service-key: STACKMAN_SERVICE_KEY
google-compute-region: STACKMAN_COMPUTE_REGION
google-compute-zone: STACKMAN_COMPUTE_ZONE
google-project-id: STACKMAN_PROJECT_ID
install-kubectl: yes
perform-login: yes
- run:
name: Deploy
command: |
echo "Current nightly pods:"
kubectl get pods --namespace nightly
echo "Redeploying pods:"
kubectl rollout restart statefulset nightly-sequencer --namespace nightly
kubectl rollout restart statefulset nightly-go-batch-submitter --namespace nightly
kubectl rollout restart statefulset nightly-dtl --namespace nightly
kubectl rollout restart deployment nightly-gas-oracle --namespace nightly
workflows:
......@@ -83,3 +123,17 @@ workflows:
context: optimism
- build-gas-oracle:
context: optimism
- build-integration-tests:
context: optimism
- build-go-batch-submitter:
context: optimism
- deploy-nightly:
context: optimism
requires:
- build-dtl
- build-batch-submitter
- build-go-batch-submitter
- build-deployer
- build-l2geth
- build-gas-oracle
- build-integration-tests
\ No newline at end of file
......@@ -28,6 +28,7 @@ jobs:
proxyd: ${{ steps.packages.outputs.proxyd }}
rpc-proxy : ${{ steps.packages.outputs.rpc-proxy }}
op-exporter : ${{ steps.packages.outputs.op-exporter }}
l2geth-exporter : ${{ steps.packages.outputs.l2geth-exporter }}
steps:
- name: Check out source code
......@@ -443,6 +444,43 @@ jobs:
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
l2geth-exporter:
name: Publish l2geth-exporter Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.l2geth-exporter != ''
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: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./go/l2geth-exporter/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.l2geth-exporter
push: true
tags: ethereumoptimism/l2geth-exporter:${{ needs.canary-publish.outputs.l2geth-exporter }}
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
rpc-proxy:
name: Publish rpc-proxy Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......
......@@ -24,6 +24,7 @@ jobs:
rpc-proxy: ${{ steps.packages.outputs.rpc-proxy }}
hardhat-node: ${{ steps.packages.outputs.hardhat-node }}
op-exporter : ${{ steps.packages.outputs.op-exporter }}
l2geth-exporter : ${{ steps.packages.outputs.l2geth-exporter }}
steps:
- name: Checkout Repo
......@@ -196,6 +197,43 @@ jobs:
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
l2geth-exporter:
name: Publish l2geth-exporter Version ${{ needs.release.outputs.l2geth-exporter}}
needs: release
if: needs.release.outputs.l2geth-exporter != ''
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: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./go/l2geth-exporter/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.l2geth-exporter
push: true
tags: ethereumoptimism/l2geth-exporter:${{ needs.release.outputs.l2geth-exporter }},ethereumoptimism/l2geth-exporter:latest
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
op-exporter:
name: Publish op-exporter Version ${{ needs.release.outputs.op-exporter}}
needs: release
......
......@@ -131,6 +131,13 @@ jobs:
fail_ci_if_error: false
verbose: true
flags: message-relayer
- uses: codecov/codecov-action@v1
with:
files: ./packages/sdk/coverage.json
fail_ci_if_error: false
verbose: true
flags: sdk
lint:
name: Linting
runs-on: ubuntu-latest
......
......@@ -293,7 +293,7 @@ func parseWalletPrivKeyAndContractAddr(
// NOTE: This method MUST be run as a goroutine.
func runMetricsServer(hostname string, port uint64) {
metricsPortStr := strconv.FormatUint(port, 10)
metricsAddr := fmt.Sprintf("%s: %s", hostname, metricsPortStr)
metricsAddr := fmt.Sprintf("%s:%s", hostname, metricsPortStr)
http.Handle("/metrics", promhttp.Handler())
_ = http.ListenAndServe(metricsAddr, nil)
......
SHELL := /bin/bash
VERSION := `git describe --abbrev=0`
GITCOMMIT := `git rev-parse HEAD`
BUILDDATE := `date +%Y-%m-%d`
BUILDUSER := `whoami`
LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT)
LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION)
LDFLAGS :=-ldflags "$(LDFLAGSSTRING)"
all: build
build:
CGO_ENABLED=0 go build $(LDFLAGS)
clean:
rm l2geth-exporter
test:
go test -v ./...
lint:
golangci-lint run ./...
binding:
$(eval temp := $(shell mktemp))
cat ../../packages/contracts/deployments/mainnet/CanonicalTransactionChain.json \
| jq -r .bytecode > $(temp)
cat ../../packages/contracts/deployments/mainnet/CanonicalTransactionChain.json \
| jq .abi \
| abigen --pkg bindings \
--abi - \
--out bindings/CanonicalTransactionChain.go \
--type CanonicalTransactionChain \
--bin $(temp)
rm $(temp)
This diff is collapsed.
package main
import (
"github.com/prometheus/client_golang/prometheus"
)
//Define the metrics we wish to expose
var (
ctcTotalElements = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "l2geth_ctc_total_elements",
Help: "CTC GetTotalElements value."},
[]string{"state"},
)
ctcTotalElementsCallSuccess = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "l2geth_ctc_total_elements_call_success",
Help: "CTC GetTotalElements call success."},
)
)
func init() {
//Register metrics with prometheus
prometheus.MustRegister(ctcTotalElements)
prometheus.MustRegister(ctcTotalElementsCallSuccess)
}
module github.com/ethereum-optimism/optimism/go/l2geth-exporter
go 1.16
require (
github.com/ethereum/go-ethereum v1.10.8
github.com/prometheus/client_golang v1.11.0
)
This diff is collapsed.
package l1contracts
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/go/l2geth-exporter/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
// CTC interacts with the OVM CTC contract
type CTC struct {
Address common.Address
Client *ethclient.Client
}
func (ctc *CTC) GetTotalElements(ctx context.Context) (*big.Int, error) {
contract, err := bindings.NewCanonicalTransactionChainCaller(ctc.Address, ctc.Client)
if err != nil {
return nil, err
}
totalElements, err := contract.GetTotalElements(&bind.CallOpts{
Context: ctx,
})
if err != nil {
return nil, err
}
return totalElements, nil
}
package main
import (
"context"
"math/big"
"net/http"
"os"
"time"
"github.com/ethereum-optimism/optimism/go/l2geth-exporter/l1contracts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
l1TimeoutSeconds = 5
)
func main() {
listenAddress := os.Getenv("LISTEN_ADDRESS")
if listenAddress == "" {
listenAddress = ":9100"
}
log.Root().SetHandler(log.CallerFileHandler(log.StdoutHandler))
l1Url := os.Getenv("L1_URL")
if l1Url == "" {
log.Error("L1_URL environmental variable is required")
os.Exit(1)
}
ctcAddress := os.Getenv("CTC_ADDRESS")
if ctcAddress == "" {
log.Error("CTC_ADDRESS environmental variable is required")
os.Exit(1)
}
client, err := ethclient.Dial(l1Url)
if err != nil {
log.Error("Problem connecting to L1: %s", err)
}
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
<head><title>L2geth Exporter</title></head>
<body>
<h1>L2geth Exporter</h1>
<p><a href="/metrics">Metrics</a></p>
</body>
</html>`))
})
go getCTCTotalElements(ctcAddress, client)
log.Info("Program starting", "listenAddress", listenAddress, "GETH_URL", l1Url, "CTC_ADDRESS", ctcAddress)
if err := http.ListenAndServe(listenAddress, nil); err != nil {
log.Error("Can't start http server", "error", err)
}
}
func getCTCTotalElements(address string, client *ethclient.Client) {
ctc := l1contracts.CTC{
Address: common.HexToAddress(address),
Client: client,
}
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(l1TimeoutSeconds))
totalElements, err := ctc.GetTotalElements(ctx)
if err != nil {
ctcTotalElementsCallSuccess.Set(0)
log.Error("Error calling GetTotalElements", "error", err)
cancel()
continue
}
ctcTotalElementsCallSuccess.Set(1)
totalElementsFloat, _ := new(big.Float).SetInt(totalElements).Float64()
ctcTotalElements.WithLabelValues(
"latest").Set(totalElementsFloat)
log.Info("ctc updated", "ctcTotalElements", totalElementsFloat)
cancel()
<-ticker.C
}
}
{
"name": "@eth-optimism/l2geth-exporter",
"version": "0.0.1",
"private": true,
"devDependencies": {}
}
......@@ -14,7 +14,7 @@ program
.option('-r, --runs <n>', 'number of runs. cannot be use with -t/--time')
.option(
'-t, --time <ms>',
'how long to run in milliseconds. cannot be used with -r/--runs',
'how long to run in milliseconds. cannot be used with -r/--runs'
)
.option('-c, --concurrency <n>', 'number of concurrent workers to spawn', '1')
.option('--think-time <n>', 'how long to wait between each run', '0')
......
......@@ -87,7 +87,10 @@ actor('Uniswap swapper', () => {
let tx = await token.transfer(wallet.address, 1000000)
await tx.wait()
const boundToken = token.connect(wallet)
tx = await boundToken.approve(contracts.positionManager.address, 1000000000)
tx = await boundToken.approve(
contracts.positionManager.address,
1000000000
)
await tx.wait()
tx = await boundToken.approve(contracts.router.address, 1000000000)
await tx.wait()
......
......@@ -6,7 +6,7 @@
"scripts": {
"lint": "yarn lint:fix && yarn lint:check",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"build": "hardhat compile",
"test:integration": "hardhat --network optimism test",
"test:actor": "IS_LIVE_NETWORK=true ts-node actor-tests/lib/runner.ts",
......
FROM golang:1.16 as builder
ADD ./go/l2geth-exporter /app/
WORKDIR /app/
RUN make build
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/l2geth-exporter /usr/local/bin/
ENTRYPOINT ["l2geth-exporter"]
CMD ["--help"]
......@@ -11,7 +11,7 @@ RUN apt-get update -y && apt-get install -y git curl jq python3
# copy over the needed configs to run the dep installation
# note: this approach can be a bit unhandy to maintain, but it allows
# us to cache the installation steps
WORKDIR /optimism
WORKDIR /opt/optimism
COPY *.json yarn.lock ./
COPY packages/core-utils/package.json ./packages/core-utils/package.json
COPY packages/common-ts/package.json ./packages/common-ts/package.json
......@@ -33,30 +33,30 @@ RUN yarn build
FROM base as deployer
WORKDIR /optimism/packages/contracts
WORKDIR /opt/optimism/packages/contracts
COPY ./ops/scripts/deployer.sh .
CMD ["yarn", "run", "deploy"]
FROM base as batch-submitter
WORKDIR /optimism/packages/batch-submitter
WORKDIR /opt/optimism/packages/batch-submitter
COPY ./ops/scripts/batches.sh .
CMD ["npm", "run", "start"]
FROM base as data-transport-layer
WORKDIR /optimism/packages/data-transport-layer
WORKDIR /opt/optimism/packages/data-transport-layer
COPY ./ops/scripts/dtl.sh .
CMD ["node", "dist/src/services/run.js"]
FROM base as integration-tests
WORKDIR /optimism/integration-tests
WORKDIR /opt/optimism/integration-tests
COPY ./ops/scripts/integration-tests.sh ./
CMD ["yarn", "test:integration"]
FROM base as relayer
WORKDIR /optimism/packages/message-relayer
WORKDIR /opt/optimism/packages/message-relayer
COPY ./ops/scripts/relayer.sh .
CMD ["npm", "run", "start"]
......@@ -11,6 +11,7 @@
"specs",
"go/gas-oracle",
"go/batch-submitter",
"go/l2geth-exporter",
"go/proxyd",
"go/op-exporter",
"ops/docker/rpc-proxy",
......
......@@ -15,7 +15,7 @@
"lint": "yarn lint:fix && yarn lint:check",
"pre-commit": "lint-staged",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"test": "hardhat test --show-stack-traces",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json"
},
......
......@@ -154,7 +154,6 @@ export class StateBatchSubmitter extends BatchSubmitter {
startBlock: number,
endBlock: number
): Promise<TransactionReceipt> {
const batchTxBuildStart = performance.now()
const batch = await this._generateStateCommitmentBatch(startBlock, endBlock)
......
......@@ -232,7 +232,7 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
const batchTxBuildEnd = performance.now()
this.metrics.batchTxBuildTime.set(batchTxBuildEnd - batchTxBuildStart)
this.metrics.numTxPerBatch.observe(endBlock - startBlock)
this.metrics.numTxPerBatch.observe(batchParams.totalElementsToAppend)
const l1tipHeight = await this.signer.provider.getBlockNumber()
this.logger.debug('Submitting batch.', {
calldata: batchParams,
......
......@@ -11,7 +11,7 @@
"all": "yarn clean && yarn build && yarn test && yarn lint:fix && yarn lint",
"build": "tsc -p tsconfig.build.json",
"clean": "rimraf dist/ ./tsconfig.build.tsbuildinfo",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"lint:fix": "yarn lint:check --fix",
"lint": "yarn lint:fix && yarn lint:check",
"pre-commit": "lint-staged",
......
......@@ -29,7 +29,7 @@
"test:slither": "slither .",
"pretest:slither": "rm -f @openzeppelin && rm -f @ens && rm -f hardhat && ln -s ../../node_modules/@openzeppelin @openzeppelin && ln -s ../../node_modules/@ens @ens && ln -s ../../node_modules/hardhat hardhat",
"posttest:slither": "rm -f @openzeppelin && rm -f @ens && rm -f hardhat",
"lint:ts:check": "eslint .",
"lint:ts:check": "eslint . --max-warnings=0",
"lint:contracts:check": "yarn solhint -f table 'contracts/**/*.sol'",
"lint:check": "yarn lint:contracts:check && yarn lint:ts:check",
"lint:ts:fix": "eslint --fix .",
......
......@@ -12,7 +12,7 @@
"build": "tsc -p tsconfig.build.json",
"clean": "rimraf dist/ ./tsconfig.build.tsbuildinfo",
"lint": "yarn lint:fix && yarn lint:check",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"lint:fix": "yarn lint:check --fix",
"pre-commit": "lint-staged",
"test": "ts-mocha test/*.spec.ts",
......
......@@ -13,7 +13,7 @@
"clean:db": "rimraf ./db",
"lint": "yarn run lint:fix && yarn run lint:check",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"start": "ts-node ./src/services/run.ts",
"start:local": "ts-node ./src/services/run.ts | pino-pretty",
"test": "hardhat --config test/config/hardhat.config.ts test",
......
#!/usr/bin/env node
const main = require('../dist/exec/run').default
;(async () => {
await main()
})().catch((err) => {
console.log(err)
process.exit(1)
})
......@@ -11,13 +11,13 @@
"withdraw": "./src/exec/withdraw.ts"
},
"scripts": {
"start": "node ./exec/run-message-relayer.js",
"start": "ts-node ./src/exec/run.ts",
"build": "tsc -p ./tsconfig.build.json",
"clean": "rimraf dist/ ./tsconfig.build.tsbuildinfo",
"lint": "yarn lint:fix && yarn lint:check",
"pre-commit": "lint-staged",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"test": "hardhat test --show-stack-traces",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json"
},
......@@ -72,6 +72,7 @@
"lodash": "^4.17.21",
"mocha": "^8.4.0",
"prettier": "^2.3.1",
"ts-node": "^10.0.0",
"typescript": "^4.3.5"
}
}
......@@ -12,7 +12,7 @@
"clean": "rimraf ./dist ./tsconfig.build.tsbuildinfo",
"lint": "yarn run lint:fix && yarn run lint:check",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"pre-commit": "lint-staged",
"test:surgery": "ts-mocha --timeout 50000000 test/*",
"start": "ts-node ./scripts/surgery.ts"
......
......@@ -12,7 +12,7 @@
"clean": "rimraf ./dist ./tsconfig.build.tsbuildinfo",
"lint": "yarn run lint:fix && yarn run lint:check",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"build": "tsc -p tsconfig.build.json",
"pre-commit": "lint-staged",
"test": "ts-mocha test/*.spec.ts",
......
......@@ -4,6 +4,13 @@
The `@eth-optimism/sdk` package provides a set of tools for interacting with Optimistic Ethereum.
## NOTICE
WARNING: This package is currently under construction.
You can definitely try to use it, but you probably won't have a good time.
Lots of pieces are missing.
We will announce the 1.0.0 release on Discord and on Twitter once it's ready, keep an eye out!
## Installation
```
......
......@@ -12,7 +12,7 @@
"build": "tsc -p tsconfig.build.json",
"clean": "rimraf dist/ ./tsconfig.build.tsbuildinfo",
"lint": "yarn lint:fix && yarn lint:check",
"lint:check": "eslint .",
"lint:check": "eslint . --max-warnings=0",
"lint:fix": "yarn lint:check --fix",
"pre-commit": "lint-staged",
"test": "hardhat test",
......
......@@ -4,7 +4,7 @@ import {
BlockTag,
TransactionReceipt,
} from '@ethersproject/abstract-provider'
import { BigNumber } from 'ethers'
import { ethers, BigNumber, Event } from 'ethers'
import {
ICrossChainProvider,
OEContracts,
......@@ -19,6 +19,8 @@ import {
MessageStatus,
TokenBridgeMessage,
MessageReceipt,
CustomBridges,
CustomBridgesLike,
} from './interfaces'
import {
toProvider,
......@@ -26,6 +28,7 @@ import {
toTransactionHash,
DeepPartial,
getAllOEContracts,
getCustomBridges,
} from './utils'
export class CrossChainProvider implements ICrossChainProvider {
......@@ -33,6 +36,7 @@ export class CrossChainProvider implements ICrossChainProvider {
public l2Provider: Provider
public l1ChainId: number
public contracts: OEContracts
public bridges: CustomBridges
/**
* Creates a new CrossChainProvider instance.
......@@ -48,6 +52,7 @@ export class CrossChainProvider implements ICrossChainProvider {
l2Provider: ProviderLike
l1ChainId: NumberLike
contracts?: DeepPartial<OEContractsLike>
bridges?: Partial<CustomBridgesLike>
}) {
this.l1Provider = toProvider(opts.l1Provider)
this.l2Provider = toProvider(opts.l2Provider)
......@@ -57,6 +62,11 @@ export class CrossChainProvider implements ICrossChainProvider {
l2SignerOrProvider: this.l2Provider,
overrides: opts.contracts,
})
this.bridges = getCustomBridges(this.l1ChainId, {
l1SignerOrProvider: this.l1Provider,
l2SignerOrProvider: this.l2Provider,
overrides: opts.bridges,
})
}
public async getMessagesByTransaction(
......@@ -115,6 +125,9 @@ export class CrossChainProvider implements ICrossChainProvider {
sender: parsed.args.sender,
message: parsed.args.message,
messageNonce: parsed.args.messageNonce,
logIndex: log.logIndex,
blockNumber: log.blockNumber,
transactionHash: log.transactionHash,
}
})
}
......@@ -132,13 +145,137 @@ export class CrossChainProvider implements ICrossChainProvider {
public async getTokenBridgeMessagesByAddress(
address: AddressLike,
opts?: {
opts: {
direction?: MessageDirection
fromBlock?: BlockTag
toBlock?: BlockTag
} = {}
): Promise<TokenBridgeMessage[]> {
const parseTokenEvent = (
event: Event,
dir: MessageDirection
): TokenBridgeMessage => {
return {
direction: dir,
from: event.args._from,
to: event.args._to,
l1Token: event.args._l1Token || ethers.constants.AddressZero,
l2Token: event.args._l2Token || this.contracts.l2.OVM_ETH.address,
amount: event.args._amount,
data: event.args._data,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
}
}
// Make sure you provide a direction if you specify a block range. Block ranges don't make
// sense to use on both chains at the same time.
if (opts.fromBlock !== undefined || opts.toBlock !== undefined) {
if (opts.direction === undefined) {
throw new Error('direction must be specified when using a block range')
}
}
// Keep track of all of the messages triggered by the address in question.
// We'll add messages to this list as we find them, based on the direction that the user has
// requested we find messages in. If the user hasn't requested a direction, we find messages in
// both directions.
const messages: TokenBridgeMessage[] = []
// First find all messages in the L1 to L2 direction.
if (
opts.direction === undefined ||
opts.direction === MessageDirection.L1_TO_L2
) {
// Find all ETH deposit events and push them into the messages array.
const ethDepositEvents =
await this.contracts.l1.L1StandardBridge.queryFilter(
this.contracts.l1.L1StandardBridge.filters.ETHDepositInitiated(
address
),
opts.fromBlock,
opts.toBlock
)
for (const event of ethDepositEvents) {
messages.push(parseTokenEvent(event, MessageDirection.L1_TO_L2))
}
// Send an event query for every L1 bridge, this will return an array of arrays.
const erc20DepositEventSets = await Promise.all(
[
this.contracts.l1.L1StandardBridge,
...Object.values(this.bridges.l1),
].map(async (bridge) => {
return bridge.queryFilter(
bridge.filters.ERC20DepositInitiated(undefined, undefined, address),
opts.fromBlock,
opts.toBlock
)
})
)
for (const erc20DepositEvents of erc20DepositEventSets) {
for (const event of erc20DepositEvents) {
messages.push(parseTokenEvent(event, MessageDirection.L1_TO_L2))
}
}
}
// Next find all messages in the L2 to L1 direction.
if (
opts.direction === undefined ||
opts.direction === MessageDirection.L2_TO_L1
) {
// ETH withdrawals and ERC20 withdrawals are the same event on L2.
// Send an event query for every L2 bridge, this will return an array of arrays.
const withdrawalEventSets = await Promise.all(
[
this.contracts.l2.L2StandardBridge,
...Object.values(this.bridges.l2),
].map(async (bridge) => {
return bridge.queryFilter(
bridge.filters.WithdrawalInitiated(undefined, undefined, address),
opts.fromBlock,
opts.toBlock
)
})
)
for (const withdrawalEvents of withdrawalEventSets) {
for (const event of withdrawalEvents) {
messages.push(parseTokenEvent(event, MessageDirection.L2_TO_L1))
}
}
}
return messages
}
public async getDepositsByAddress(
address: AddressLike,
opts: {
fromBlock?: BlockTag
toBlock?: BlockTag
} = {}
): Promise<TokenBridgeMessage[]> {
throw new Error('Not implemented')
return this.getTokenBridgeMessagesByAddress(address, {
...opts,
direction: MessageDirection.L1_TO_L2,
})
}
public async getWithdrawalsByAddress(
address: AddressLike,
opts: {
fromBlock?: BlockTag
toBlock?: BlockTag
} = {}
): Promise<TokenBridgeMessage[]> {
return this.getTokenBridgeMessagesByAddress(address, {
...opts,
direction: MessageDirection.L2_TO_L1,
})
}
public async getMessageStatus(message: MessageLike): Promise<MessageStatus> {
......
......@@ -11,6 +11,7 @@ import {
TokenBridgeMessage,
OEContracts,
MessageReceipt,
CustomBridges,
} from './types'
/**
......@@ -38,6 +39,11 @@ export interface ICrossChainProvider {
*/
contracts: OEContracts
/**
* List of custom bridges for the given network.
*/
bridges: CustomBridges
/**
* Retrieves all cross chain messages sent within a given transaction.
*
......@@ -80,7 +86,7 @@ export interface ICrossChainProvider {
/**
* Finds all cross chain messages that correspond to token deposits or withdrawals sent by a
* particular address. Useful for finding deposits/withdrawals because the sender of the message
* will appear to be the StandardBridge contract and not the actual end user. Returns
* will appear to be the StandardBridge contract and not the actual end user.
*
* @param address Address to search for messages from.
* @param opts Options object.
......@@ -101,6 +107,44 @@ export interface ICrossChainProvider {
}
): Promise<TokenBridgeMessage[]>
/**
* Alias for getTokenBridgeMessagesByAddress with a drection of L1_TO_L2.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All deposit token bridge messages sent by the given address.
*/
getDepositsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]>
/**
* Alias for getTokenBridgeMessagesByAddress with a drection of L2_TO_L1.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All withdrawal token bridge messages sent by the given address.
*/
getWithdrawalsByAddress(
address: AddressLike,
opts?: {
fromBlock?: BlockTag
toBlock?: BlockTag
}
): Promise<TokenBridgeMessage[]>
/**
* Retrieves the status of a particular message as an enum.
*
......
......@@ -67,6 +67,30 @@ export interface OEContractsLike {
l2: OEL2ContractsLike
}
/**
* Represents list of custom bridges.
*/
export interface CustomBridges {
l1: {
[name: string]: Contract
}
l2: {
[name: string]: Contract
}
}
/**
* Something that looks like the list of custom bridges.
*/
export interface CustomBridgesLike {
l1: {
[K in keyof CustomBridges['l1']]: AddressLike
}
l2: {
[K in keyof CustomBridges['l2']]: AddressLike
}
}
/**
* Enum describing the status of a message.
*/
......@@ -123,11 +147,9 @@ export interface CrossChainMessageRequest {
}
/**
* Describes a message that is sent between L1 and L2. Direction determines where the message was
* sent from and where it's being sent to.
* Core components of a cross chain message.
*/
export interface CrossChainMessage {
direction: MessageDirection
export interface CoreCrossChainMessage {
sender: string
target: string
message: string
......@@ -135,12 +157,15 @@ export interface CrossChainMessage {
}
/**
* Convenience type for when you don't care which direction the message is going in.
* Describes a message that is sent between L1 and L2. Direction determines where the message was
* sent from and where it's being sent to.
*/
export type DirectionlessCrossChainMessage = Omit<
CrossChainMessage,
'direction'
>
export interface CrossChainMessage extends CoreCrossChainMessage {
direction: MessageDirection
logIndex: number
blockNumber: number
transactionHash: string
}
/**
* Describes a token withdrawal or deposit, along with the underlying raw cross chain message
......@@ -153,7 +178,10 @@ export interface TokenBridgeMessage {
l1Token: string
l2Token: string
amount: BigNumber
raw: CrossChainMessage
data: string
logIndex: number
blockNumber: number
transactionHash: string
}
/**
......
......@@ -7,6 +7,8 @@ import {
OEContractsLike,
OEL2ContractsLike,
AddressLike,
CustomBridges,
CustomBridgesLike,
} from '../interfaces'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
......@@ -36,6 +38,42 @@ const NAME_REMAPPING = {
WETH: 'WETH9',
}
/**
* Mapping of L1 chain IDs to the list of custom bridge addresses for each chain.
*/
export const CUSTOM_BRIDGE_ADDRESSES: {
[l1ChainId: number]: CustomBridgesLike
} = {
// TODO: Maybe we can pull these automatically from the token list?
// Alternatively, check against the token list in CI.
1: {
l1: {
SNX: '0xCd9D4988C0AE61887B075bA77f08cbFAd2b65068',
DAI: '0x10E6593CDda8c58a1d0f14C5164B376352a55f2F',
BitBTC: '0xaBA2c5F108F7E820C049D5Af70B16ac266c8f128',
},
l2: {
SNX: '0x3f87Ff1de58128eF8FCb4c807eFD776E1aC72E51',
DAI: '0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65',
BitBTC: '0x158F513096923fF2d3aab2BcF4478536de6725e2',
},
},
42: {
l1: {
SNX: '0xD134Db47DDF5A6feB245452af17cCAf92ee53D3c',
DAI: '0xb415e822C4983ecD6B1c1596e8a5f976cf6CD9e3',
BitBTC: '0x0b651A42F32069d62d5ECf4f2a7e5Bd3E9438746',
USX: '0x40E862341b2416345F02c41Ac70df08525150dC7',
},
l2: {
SNX: '0x5C3f51CEd0C2F6157e2be67c029264D6C44bfe42',
DAI: '0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65',
BitBTC: '0x0CFb46528a7002a7D8877a5F7a69b9AaF1A9058e',
USX: '0xB4d37826b14Cd3CB7257A2A5094507d701fe715f',
},
},
}
/**
* Mapping of L1 chain IDs to the appropriate contract addresses for the OE deployments to the
* given network. Simplifies the process of getting the correct contract addresses for a given
......@@ -190,3 +228,59 @@ export const getAllOEContracts = (
l2: l2Contracts,
}
}
/**
* Gets a series of custom bridges for the given L1 chain ID.
*
* @param l1ChainId L1 chain ID for the L1 network where the custom bridges are deployed.
* @param opts Additional options for connecting to the custom bridges.
* @param opts.l1SignerOrProvider Signer or provider to connect to the L1 contracts.
* @param opts.l2SignerOrProvider Signer or provider to connect to the L2 contracts.
* @param opts.overrides Custom contract address overrides for L1 or L2 contracts.
* @returns An object containing ethers.Contract objects connected to the appropriate addresses on
* both L1 and L2.
*/
export const getCustomBridges = (
l1ChainId: number,
opts: {
l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
overrides?: Partial<CustomBridgesLike>
} = {}
): CustomBridges => {
const addresses = CUSTOM_BRIDGE_ADDRESSES[l1ChainId] || {
l1: {},
l2: {},
}
for (const [contractName, contractAddress] of Object.entries(
opts.overrides?.l1 || {}
)) {
addresses.l1[contractName] = contractAddress
}
for (const [contractName, contractAddress] of Object.entries(
opts.overrides?.l2 || {}
)) {
addresses.l2[contractName] = contractAddress
}
const bridges = {
l1: {},
l2: {},
}
for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
bridges.l1[contractName] = new Contract(
toAddress(contractAddress),
getContractInterface('IL1ERC20Bridge'),
opts.l1SignerOrProvider
)
}
for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
bridges.l2[contractName] = new Contract(
toAddress(contractAddress),
getContractInterface('IL2ERC20Bridge'),
opts.l2SignerOrProvider
)
}
return bridges
}
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { DirectionlessCrossChainMessage } from '../interfaces'
import { CoreCrossChainMessage } from '../interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
......@@ -10,7 +10,7 @@ import { DirectionlessCrossChainMessage } from '../interfaces'
* @returns Canonical encoding of the message.
*/
export const encodeCrossChainMessage = (
message: DirectionlessCrossChainMessage
message: CoreCrossChainMessage
): string => {
return getContractInterface('L2CrossDomainMessenger').encodeFunctionData(
'relayMessage',
......@@ -27,7 +27,7 @@ export const encodeCrossChainMessage = (
* @returns Canonical hash of the message.
*/
export const hashCrossChainMessage = (
message: DirectionlessCrossChainMessage
message: CoreCrossChainMessage
): string => {
return ethers.utils.solidityKeccak256(
['bytes'],
......
pragma solidity ^0.8.9;
import { MockMessenger } from "./MockMessenger.sol";
contract MockBridge {
event ERC20DepositInitiated(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _amount,
bytes _data
);
event ERC20WithdrawalFinalized(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _amount,
bytes _data
);
event WithdrawalInitiated(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _amount,
bytes _data
);
event DepositFinalized(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _amount,
bytes _data
);
event DepositFailed(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _amount,
bytes _data
);
struct TokenEventStruct {
address l1Token;
address l2Token;
address from;
address to;
uint256 amount;
bytes data;
}
MockMessenger public messenger;
constructor(MockMessenger _messenger) {
messenger = _messenger;
}
function emitERC20DepositInitiated(
TokenEventStruct memory _params
) public {
emit ERC20DepositInitiated(_params.l1Token, _params.l2Token, _params.from, _params.to, _params.amount, _params.data);
messenger.triggerSentMessageEvent(
MockMessenger.SentMessageEventParams(
address(0),
address(0),
hex"1234",
1234,
12345678
)
);
}
function emitERC20WithdrawalFinalized(
TokenEventStruct memory _params
) public {
emit ERC20WithdrawalFinalized(_params.l1Token, _params.l2Token, _params.from, _params.to, _params.amount, _params.data);
}
function emitWithdrawalInitiated(
TokenEventStruct memory _params
) public {
emit WithdrawalInitiated(_params.l1Token, _params.l2Token, _params.from, _params.to, _params.amount, _params.data);
messenger.triggerSentMessageEvent(
MockMessenger.SentMessageEventParams(
address(0),
address(0),
hex"1234",
1234,
12345678
)
);
}
function emitDepositFinalized(
TokenEventStruct memory _params
) public {
emit DepositFinalized(_params.l1Token, _params.l2Token, _params.from, _params.to, _params.amount, _params.data);
}
function emitDepositFailed(
TokenEventStruct memory _params
) public {
emit DepositFailed(_params.l1Token, _params.l2Token, _params.from, _params.to, _params.amount, _params.data);
}
}
......@@ -28,17 +28,23 @@ contract MockMessenger is ICrossDomainMessenger {
return;
}
function triggerSentMessageEvent(
SentMessageEventParams memory _params
) public {
emit SentMessage(
_params.target,
_params.sender,
_params.message,
_params.messageNonce,
_params.gasLimit
);
}
function triggerSentMessageEvents(
SentMessageEventParams[] memory _params
) public {
for (uint256 i = 0; i < _params.length; i++) {
emit SentMessage(
_params[i].target,
_params[i].sender,
_params[i].message,
_params[i].messageNonce,
_params[i].gasLimit
);
triggerSentMessageEvent(_params[i]);
}
}
......
......@@ -3,8 +3,7 @@ import { Contract, Signer } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import {
CrossChainMessage,
MessageDirection,
CoreCrossChainMessage,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../../src'
......@@ -25,8 +24,7 @@ describe('message encoding utils', () => {
})
it('should properly encode a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
const message: CoreCrossChainMessage = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
......@@ -53,8 +51,7 @@ describe('message encoding utils', () => {
})
it('should properly hash a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
const message: CoreCrossChainMessage = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
......
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