├── bindings-go
├── .gitignore
├── artifacts.json
├── bindings
│ ├── IRollup_more.go
│ ├── IChallenge_more.go
│ ├── ISymChallenge_more.go
│ ├── IAsymChallenge_more.go
│ ├── ISequencerInbox_more.go
│ ├── IChallengeResultReceiver_more.go
│ ├── L2PortalDeterministicStorage_more.go
│ ├── registry.go
│ └── ERC1967Proxy_more.go
├── go.mod
└── Makefile
├── .dockerignore
├── packages
├── onboarding
│ ├── .dockerignore
│ ├── .gitignore
│ ├── index.ts
│ ├── src
│ │ ├── onboarding
│ │ │ ├── index.ts
│ │ │ ├── utils.ts
│ │ │ ├── config.ts
│ │ │ ├── types.ts
│ │ │ ├── messenger.ts
│ │ │ └── service.ts
│ │ └── index.ts
│ ├── .env.example
│ ├── README.md
│ ├── tsconfig.json
│ ├── .eslintrc.json
│ └── package.json
└── sdk
│ ├── src
│ ├── utils
│ │ └── index.ts
│ ├── interfaces
│ │ ├── index.ts
│ │ └── types.ts
│ ├── index.ts
│ └── constants.ts
│ ├── .gitignore
│ ├── .env.test
│ ├── .env.test.example
│ ├── tsconfig.json
│ ├── .eslintrc.json
│ ├── README.md
│ ├── scripts
│ ├── test_withdrawalERC20.ts
│ ├── test_depositERC20.ts
│ └── test_deposit.ts
│ └── package.json
├── services
└── sidecar
│ ├── config
│ └── default.yaml
│ ├── .gitignore
│ ├── internal
│ └── service
│ │ ├── di
│ │ ├── state_provider.go
│ │ ├── common_provider.go
│ │ ├── config_provider.go
│ │ ├── service_provider.go
│ │ └── inject.go
│ │ └── config
│ │ ├── logger.go
│ │ ├── context.go
│ │ └── cli_config.go
│ ├── Dockerfile
│ ├── rollup
│ ├── rpc
│ │ └── eth
│ │ │ ├── txmgr
│ │ │ └── metrics
│ │ │ │ └── noop.go
│ │ │ ├── tag.go
│ │ │ ├── utils.go
│ │ │ └── eth_syncer.go
│ ├── services
│ │ ├── disseminator
│ │ │ └── interface.go
│ │ └── validator
│ │ │ └── interface.go
│ └── types
│ │ └── block_id.go
│ ├── Makefile
│ ├── cmd
│ └── sidecar
│ │ └── main.go
│ ├── utils
│ ├── log
│ │ └── root.go
│ ├── errors.go
│ ├── fmt
│ │ └── fmt.go
│ ├── map.go
│ └── retry
│ │ ├── operation.go
│ │ └── strategy.go
│ ├── proof
│ ├── proof
│ │ └── proof.go
│ └── prover
│ │ ├── debug_prover.go
│ │ ├── state_generator.go
│ │ └── one_step_prover.go
│ └── README.md
├── .actrc
├── pnpm-workspace.yaml
├── .gitignore
├── .husky
├── pre-push
└── pre-commit
├── ops
├── .gitignore
├── genesis
│ └── helpers.go
├── README.md
└── predeploys
│ ├── addresses.go
│ └── helpers.go
├── config
├── example
│ ├── jwt_secret.txt
│ ├── sequencer_pk.txt
│ ├── validator_pk.txt
│ ├── base_sp_rollup.json
│ ├── blockscout
│ │ └── blockscout.env
│ ├── .contracts.env
│ ├── .paths.env
│ ├── .sp_geth.env
│ ├── .sp_magi.env
│ ├── genesis_config.json
│ ├── .sidecar.env
│ └── .genesis.env
├── e2e_test
│ ├── jwt_secret.txt
│ ├── base_sp_rollup.json
│ ├── .sp_geth.env
│ ├── .genesis.env
│ ├── .sp_magi.env
│ ├── .sidecar.env
│ ├── .paths.env
│ └── genesis_config.json
├── kubernetes
│ ├── jwt_secret.txt
│ ├── base_sp_rollup.json
│ ├── .contracts.env
│ ├── .sp_geth.env
│ ├── .genesis.env
│ ├── .paths.env
│ ├── .sidecar.env
│ ├── .sp_magi.env
│ └── genesis_config.json
├── local_devnet
│ ├── jwt_secret.txt
│ ├── base_sp_rollup.json
│ ├── .sp_geth.env
│ ├── .genesis.env
│ ├── .sp_magi.env
│ ├── .sidecar.env
│ ├── .paths.env
│ └── genesis_config.json
└── local_docker
│ ├── jwt_secret.txt
│ ├── base_sp_rollup.json
│ ├── .contracts.env
│ ├── .sp_geth.env
│ ├── .genesis.env
│ ├── .paths.env
│ ├── .sp_magi.env
│ ├── .sidecar.env
│ └── genesis_config.json
├── charts
└── specular
│ ├── Chart.yaml
│ └── templates
│ ├── sp-workspace-persistentvolume-claim.yaml
│ ├── config.yaml
│ ├── l1-geth-service.yaml
│ ├── sp-geth-service.yaml
│ ├── sp-workspace-persistentvolume.yaml
│ ├── ingress.yaml
│ ├── tx-fuzz-pod.yaml
│ ├── debug-pod.yaml
│ ├── generator-pod.yaml
│ ├── sidecar-pod.yaml
│ ├── l1-geth-pod.yaml
│ └── sp-magi-pod.yaml
├── .github
├── pull_request_template.md
├── actions
│ ├── go
│ │ └── action.yml
│ ├── foundry
│ │ └── action.yml
│ └── node
│ │ └── action.yml
├── stale-workflows
│ ├── README.md
│ └── slither.yml
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── test.yml
│ ├── release.yml
│ ├── release-snapshot.yml
│ └── lint.yml
├── .changeset
├── pink-chefs-change.md
├── config.json
└── README.md
├── contracts
├── .lintstagedrc.json
├── remappings.txt
├── example.env
├── .gitignore
├── src
│ ├── pre-deploy
│ │ ├── L1FeeVault.sol
│ │ ├── L2BaseFeeVault.sol
│ │ └── UUPSPlaceholder.sol
│ ├── test
│ │ └── TestToken.sol
│ ├── libraries
│ │ ├── Types.sol
│ │ ├── SafeCall.sol
│ │ ├── Predeploys.sol
│ │ ├── Hashing.sol
│ │ ├── DeserializationLib.sol
│ │ ├── Errors.sol
│ │ ├── Encoding.sol
│ │ ├── FeeVault.sol
│ │ └── trie
│ │ │ └── SecureMerkleTrie.sol
│ ├── vendor
│ │ ├── ERC1967Proxy.sol
│ │ └── AddressAliasHelper.sol
│ ├── bridge
│ │ ├── mintable
│ │ │ └── IMintableERC20.sol
│ │ └── IL2Portal.sol
│ ├── IDAProvider.sol
│ ├── challenge
│ │ └── verifier
│ │ │ ├── VerificationContextLib.sol
│ │ │ ├── IVerifier.sol
│ │ │ ├── Verifier.sol
│ │ │ └── IVerificationContext.sol
│ └── ISequencerInbox.sol
├── tsconfig.json
├── slither.config.json
├── scripts
│ ├── e2e
│ │ ├── addresses.ts
│ │ └── test_transactions.ts
│ ├── config
│ │ ├── utils.ts
│ │ ├── set_rollup_genesis_state.ts
│ │ └── create_deployments_config.ts
│ ├── bridge
│ │ ├── confirm_oracle.ts
│ │ └── deployERC20Token.ts
│ └── upgrade_placeholder.ts
├── deploy
│ └── l1
│ │ ├── 01_deploy_Verifier.ts
│ │ ├── 00_deploy_SequencerInbox.ts
│ │ ├── 03_deploy_L1Portal.ts
│ │ ├── 04_deploy_L1StandardBridge.ts
│ │ └── 02_deploy_Rollup.ts
├── foundry.toml
├── .eslintrc.json
├── Dockerfile
├── package.json
└── test
│ └── utils
│ └── Utils.sol
├── docs
├── e2e.md
├── contracts.md
├── architecture.md
└── deployingERC20.md
├── .editorconfig
├── sbin
├── clean.sh
├── clean_sp_geth.sh
├── utils
│ ├── crypto.sh
│ └── utils.sh
├── generate_jwt_secret.sh
├── entrypoint.sh
├── clean_deployment.sh
├── start_sidecar.sh
├── start_sp_magi.sh
└── create_genesis.sh
├── .gitmodules
├── package.json
├── docker
├── e2e.Dockerfile
├── build-master.Dockerfile
└── specular.Dockerfile
└── lib
└── el_golang_lib
└── hook
└── fee_hook_test.go
/bindings-go/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | **/node_modules/
--------------------------------------------------------------------------------
/packages/onboarding/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/packages/sdk/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./helpers";
2 |
--------------------------------------------------------------------------------
/packages/sdk/src/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./types";
2 |
--------------------------------------------------------------------------------
/services/sidecar/config/default.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | LOG_LEVEL: "DEBUG"
3 |
--------------------------------------------------------------------------------
/.actrc:
--------------------------------------------------------------------------------
1 | --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'contracts'
3 | - 'packages/*'
4 |
--------------------------------------------------------------------------------
/services/sidecar/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | bindings/I*
3 | bindings/L1Oracle.go
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 |
4 | /workspace
5 | node_modules
6 | artifacts
7 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 | make
4 |
--------------------------------------------------------------------------------
/ops/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | bindings/bindings/*.go
3 | !bindings/bindings/registry.go
4 |
--------------------------------------------------------------------------------
/config/example/jwt_secret.txt:
--------------------------------------------------------------------------------
1 | d859509273b3f89381f664760e6951336fc488d4f17fd2d26f76d2d6fe681aef
2 |
--------------------------------------------------------------------------------
/config/example/sequencer_pk.txt:
--------------------------------------------------------------------------------
1 | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
--------------------------------------------------------------------------------
/packages/sdk/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | # typechain bindings
4 | src/types/contracts
--------------------------------------------------------------------------------
/packages/sdk/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./interfaces";
2 | export * from "./service_bridge";
3 |
--------------------------------------------------------------------------------
/config/e2e_test/jwt_secret.txt:
--------------------------------------------------------------------------------
1 | bf549f5188556ce0951048ef467ec93067bc4ea21acebe46ef675cd4e8e015ff
2 |
--------------------------------------------------------------------------------
/config/example/validator_pk.txt:
--------------------------------------------------------------------------------
1 | 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
2 |
--------------------------------------------------------------------------------
/config/kubernetes/jwt_secret.txt:
--------------------------------------------------------------------------------
1 | bf549f5188556ce0951048ef467ec93067bc4ea21acebe46ef675cd4e8e015ff
2 |
--------------------------------------------------------------------------------
/config/local_devnet/jwt_secret.txt:
--------------------------------------------------------------------------------
1 | bf549f5188556ce0951048ef467ec93067bc4ea21acebe46ef675cd4e8e015ff
2 |
--------------------------------------------------------------------------------
/config/local_docker/jwt_secret.txt:
--------------------------------------------------------------------------------
1 | bf549f5188556ce0951048ef467ec93067bc4ea21acebe46ef675cd4e8e015ff
2 |
--------------------------------------------------------------------------------
/packages/sdk/.env.test:
--------------------------------------------------------------------------------
1 | L1_URL=http://localhost:8545
2 | L2_URL=http://localhost:4011
3 | L1_CHAIN_ID=1337
4 | L2_CHAIN_ID=13527
5 |
--------------------------------------------------------------------------------
/packages/onboarding/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | coverage
4 | .openzeppelin
5 | coverage.json
6 | typechain
7 | nohup.out
8 | dist/
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 | shfmt -d sbin
4 | pnpm --filter ./contracts/ run lint:fix
5 |
--------------------------------------------------------------------------------
/packages/sdk/.env.test.example:
--------------------------------------------------------------------------------
1 | L1_URL=http://localhost:8545
2 | L2_URL=http://localhost:4011
3 | L1_CHAIN_ID=1337
4 | L2_CHAIN_ID=13527
5 |
--------------------------------------------------------------------------------
/packages/onboarding/index.ts:
--------------------------------------------------------------------------------
1 | import main from "./src";
2 |
3 | main().catch((err) => {
4 | console.error(err);
5 | process.exit(1);
6 | });
7 |
--------------------------------------------------------------------------------
/charts/specular/Chart.yaml:
--------------------------------------------------------------------------------
1 | name: specular
2 | description: Specular L2 Network
3 | version: 0.0.1
4 | apiVersion: v2
5 | keywords:
6 | - specular
7 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Goals of PR
2 |
3 | Core changes:
4 |
5 | - Describe changes here
6 |
7 | Notes:
8 |
9 | - Write notes here
10 |
--------------------------------------------------------------------------------
/config/example/base_sp_rollup.json:
--------------------------------------------------------------------------------
1 | {
2 | "block_time": 2,
3 | "max_sequencer_drift": 600,
4 | "seq_window_size": 3600,
5 | "l1_chain_id": 1337
6 | }
7 |
--------------------------------------------------------------------------------
/config/e2e_test/base_sp_rollup.json:
--------------------------------------------------------------------------------
1 | {
2 | "block_time": 2,
3 | "max_sequencer_drift": 600,
4 | "seq_window_size": 3600,
5 | "l1_chain_id": 1337
6 | }
7 |
--------------------------------------------------------------------------------
/config/kubernetes/base_sp_rollup.json:
--------------------------------------------------------------------------------
1 | {
2 | "block_time": 2,
3 | "max_sequencer_drift": 600,
4 | "seq_window_size": 3600,
5 | "l1_chain_id": 1337
6 | }
7 |
--------------------------------------------------------------------------------
/config/local_devnet/base_sp_rollup.json:
--------------------------------------------------------------------------------
1 | {
2 | "block_time": 2,
3 | "max_sequencer_drift": 600,
4 | "seq_window_size": 3600,
5 | "l1_chain_id": 1337
6 | }
7 |
--------------------------------------------------------------------------------
/config/local_docker/base_sp_rollup.json:
--------------------------------------------------------------------------------
1 | {
2 | "block_time": 2,
3 | "max_sequencer_drift": 600,
4 | "seq_window_size": 3600,
5 | "l1_chain_id": 1337
6 | }
7 |
--------------------------------------------------------------------------------
/.changeset/pink-chefs-change.md:
--------------------------------------------------------------------------------
1 | ---
2 | "@specularl2/sdk": patch
3 | "@specularl2/contracts": patch
4 | "@specularl2/onboarding-services": patch
5 | ---
6 |
7 | Initial changeset
8 |
--------------------------------------------------------------------------------
/contracts/.lintstagedrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "*.js": [
3 | "prettier --write",
4 | "eslint"
5 | ],
6 | "*.ts": [
7 | "prettier --write",
8 | "eslint"
9 | ]
10 | }
--------------------------------------------------------------------------------
/config/e2e_test/.sp_geth.env:
--------------------------------------------------------------------------------
1 | NETWORK_ID=13527
2 | ADDRESS=0.0.0.0
3 | HTTP_PORT=4011
4 | WS_PORT=4012
5 | AUTH_PORT=4013
6 | GENESIS_PATH=./genesis.json
7 | DATA_DIR=./sp-geth-data/
8 | JWT_SECRET_PATH=./jwt_secret.txt
9 |
--------------------------------------------------------------------------------
/config/kubernetes/.contracts.env:
--------------------------------------------------------------------------------
1 | SEQUENCER_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
2 | VALIDATOR_ADDRESS=0x70997970c51812dc3a010c7d01b50e0d17dc79c8
3 | DEPLOYER_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
--------------------------------------------------------------------------------
/config/local_docker/.contracts.env:
--------------------------------------------------------------------------------
1 | SEQUENCER_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
2 | VALIDATOR_ADDRESS=0x70997970c51812dc3a010c7d01b50e0d17dc79c8
3 | DEPLOYER_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
--------------------------------------------------------------------------------
/config/kubernetes/.sp_geth.env:
--------------------------------------------------------------------------------
1 | NETWORK_ID=13527
2 | ADDRESS=0.0.0.0
3 | HTTP_PORT=4011
4 | WS_PORT=4012
5 | AUTH_PORT=4013
6 | GENESIS_PATH=./genesis.json
7 | DATA_DIR=./sp-geth-data/
8 | JWT_SECRET_PATH=./jwt_secret.txt
9 |
--------------------------------------------------------------------------------
/config/local_devnet/.sp_geth.env:
--------------------------------------------------------------------------------
1 | NETWORK_ID=13527
2 | ADDRESS=0.0.0.0
3 | HTTP_PORT=4011
4 | WS_PORT=4012
5 | AUTH_PORT=4013
6 | GENESIS_PATH=./genesis.json
7 | DATA_DIR=./sp-geth-data/
8 | JWT_SECRET_PATH=./jwt_secret.txt
9 |
--------------------------------------------------------------------------------
/config/local_docker/.sp_geth.env:
--------------------------------------------------------------------------------
1 | NETWORK_ID=13527
2 | ADDRESS=0.0.0.0
3 | HTTP_PORT=4011
4 | WS_PORT=4012
5 | AUTH_PORT=4013
6 | GENESIS_PATH=./genesis.json
7 | DATA_DIR=./sp-geth-data/
8 | JWT_SECRET_PATH=./jwt_secret.txt
9 |
--------------------------------------------------------------------------------
/packages/onboarding/src/onboarding/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./config";
2 | export * from "./messenger";
3 | export * from "./plugin";
4 | export * from "./service";
5 | export * from "./types";
6 | export * from "./utils";
7 |
--------------------------------------------------------------------------------
/.github/actions/go/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Setup go'
2 |
3 | runs:
4 | using: "composite"
5 | steps:
6 | - uses: actions/setup-go@v4
7 | with:
8 | go-version: '1.20'
9 | cache-dependency-path: "**/*.sum"
10 |
--------------------------------------------------------------------------------
/docs/e2e.md:
--------------------------------------------------------------------------------
1 | # E2E test-suite
2 |
3 | **Run tests**
4 |
5 | ```sh
6 | ./sbin/run_e2e_tests.sh
7 | ```
8 |
9 | **Cleanup**
10 |
11 | Run if the above command failed to clean up due to an error.
12 |
13 | ```sh
14 | ./sbin/clean.sh
15 | ```
16 |
--------------------------------------------------------------------------------
/packages/sdk/src/constants.ts:
--------------------------------------------------------------------------------
1 | // Pre-deploy contract addresses
2 | export const l1OracleAddress =
3 | "0x2A00000000000000000000000000000000000010" as const;
4 | export const l2PortalAddress =
5 | "0x2A00000000000000000000000000000000000011" as const;
6 |
--------------------------------------------------------------------------------
/contracts/remappings.txt:
--------------------------------------------------------------------------------
1 | @ensdomains/=node_modules/@ensdomains/
2 | @openzeppelin/=node_modules/@openzeppelin/
3 | ds-test/=lib/forge-std/lib/ds-test/src/
4 | forge-std/=lib/forge-std/src/
5 | hardhat-deploy/=node_modules/hardhat-deploy/
6 | hardhat/=node_modules/hardhat/
7 |
--------------------------------------------------------------------------------
/contracts/example.env:
--------------------------------------------------------------------------------
1 | # this is a sample file
2 | INFURA_KEY=abcdef12345
3 | SEQUENCER_PRIVATE_KEY=fake8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8fake
4 | ALCHEMY_KEY=abcdef12345
5 | ETHERSCAN_API_KEY=KEY_PLACEHOLDER
6 | SEQUENCER_ADDRESS=0xSEQUENCER_PUBLIC_ADDRESS
7 |
--------------------------------------------------------------------------------
/ops/genesis/helpers.go:
--------------------------------------------------------------------------------
1 | package genesis
2 |
3 | import (
4 | "math/big"
5 |
6 | "github.com/ethereum/go-ethereum/common/hexutil"
7 | )
8 |
9 | func newHexBig(in uint64) *hexutil.Big {
10 | b := new(big.Int).SetUint64(in)
11 | hb := hexutil.Big(*b)
12 | return &hb
13 | }
14 |
--------------------------------------------------------------------------------
/packages/onboarding/.env.example:
--------------------------------------------------------------------------------
1 | # Application port exposing the API
2 | APP_PORT=10884
3 | L1_PROVIDER_ENDPOINT=
4 | L2_PROVIDER_ENDPOINT=
5 | L2_FUNDER_PRIVATE_KEY=
6 | L1_PORTAL_ADDRESS=
7 | # The threshold of user funds on L2. Leave blank for default 0.01 ETH.
8 | DEPOSIT_FUNDING_THRESHOLD=
--------------------------------------------------------------------------------
/contracts/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | /*.env
4 | coverage
5 | .openzeppelin
6 | coverage.json
7 | typechain
8 |
9 | # Hardhat files
10 | abi
11 | cache
12 | artifacts
13 | deployments
14 | typechain-types
15 |
16 | # Foundry
17 | forge-cache
18 | out
19 |
20 | # Local log output
21 | l1.log
22 |
--------------------------------------------------------------------------------
/packages/onboarding/src/onboarding/utils.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 |
3 | export function getStorageKey(messageHash: string) {
4 | return ethers.utils.keccak256(
5 | ethers.utils.defaultAbiCoder.encode(
6 | ["bytes32", "uint256"],
7 | [messageHash, 0]
8 | )
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # defaults
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.sol]
13 | indent_size = 4
14 |
15 | [*.go]
16 | indent_size = 4
17 | indent_style = tab
18 |
--------------------------------------------------------------------------------
/packages/onboarding/src/onboarding/config.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers";
2 |
3 | export type OnboardingServiceConfig = {
4 | l1ProviderEndpoint: string;
5 | l2ProviderEndpoint: string;
6 | l2FunderPrivateKey: string;
7 | l1PortalAddress: string;
8 | depositFundingThreshold: BigNumber;
9 | };
10 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/di/state_provider.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/google/wire"
5 |
6 | "github.com/specularL2/specular/services/sidecar/internal/sidecar/infra/services"
7 | )
8 |
9 | var L1StateProvider = wire.NewSet( //nolint:gochecknoglobals
10 | services.NewL1State,
11 | )
12 |
--------------------------------------------------------------------------------
/.github/stale-workflows/README.md:
--------------------------------------------------------------------------------
1 | # stale-workflows
2 |
3 | This folder contains stale workflows that are either not currently working or not used but might be needed in the future.
4 |
5 | - `slither.yml` - Slither static analysis for the project. Currently not working due to https://github.com/crytic/crytic-compile/pull/515.
6 |
--------------------------------------------------------------------------------
/config/local_docker/.genesis.env:
--------------------------------------------------------------------------------
1 | GENESIS_CFG_PATH=./genesis_config.json
2 | GENESIS_PATH=./genesis.json
3 | BASE_ROLLUP_CFG_PATH=./base_sp_rollup.json
4 | ROLLUP_CFG_PATH=./sp_rollup.json
5 | GENESIS_EXPORTED_HASH_PATH=./genesis_hash.json
6 | L1_NETWORK=localhost
7 | L1_STACK=geth
8 | L1_ENDPOINT=ws://0.0.0.0:8545
9 | L1_PERIOD=12
10 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/config/e2e_test/.genesis.env:
--------------------------------------------------------------------------------
1 | GENESIS_CFG_PATH=./genesis_config.json
2 | GENESIS_PATH=./genesis.json
3 | BASE_ROLLUP_CFG_PATH=./base_sp_rollup.json
4 | ROLLUP_CFG_PATH=./sp_rollup.json
5 | GENESIS_EXPORTED_HASH_PATH=./genesis_hash.json
6 |
7 | L1_NETWORK=localhost
8 | L1_STACK=geth
9 | L1_PERIOD=2
10 | L1_ENDPOINT=ws://127.0.0.1:8545
11 |
--------------------------------------------------------------------------------
/config/kubernetes/.genesis.env:
--------------------------------------------------------------------------------
1 | GENESIS_CFG_PATH=./genesis_config.json
2 | GENESIS_PATH=./genesis.json
3 | BASE_ROLLUP_CFG_PATH=./base_sp_rollup.json
4 | ROLLUP_CFG_PATH=./sp_rollup.json
5 | GENESIS_EXPORTED_HASH_PATH=./genesis_hash.json
6 |
7 | L1_NETWORK=localhost
8 | L1_STACK=geth
9 | L1_ENDPOINT=ws://0.0.0.0:8545
10 | L1_PERIOD=12
11 |
--------------------------------------------------------------------------------
/config/local_devnet/.genesis.env:
--------------------------------------------------------------------------------
1 | GENESIS_CFG_PATH=./genesis_config.json
2 | GENESIS_PATH=./genesis.json
3 | BASE_ROLLUP_CFG_PATH=./base_sp_rollup.json
4 | ROLLUP_CFG_PATH=./sp_rollup.json
5 | GENESIS_EXPORTED_HASH_PATH=./genesis_hash.json
6 |
7 | L1_NETWORK=localhost
8 | L1_STACK=geth
9 | L1_PERIOD=2
10 | L1_ENDPOINT=ws://127.0.0.1:8545
11 |
--------------------------------------------------------------------------------
/config/local_docker/.paths.env:
--------------------------------------------------------------------------------
1 | BIN_DIR=/usr/local/bin
2 | CONTRACTS_DIR=/specular/contracts
3 | CONFIG_DIR=/specular/config
4 | OPS_GENESIS_BIN=/usr/local/bin/genesis
5 | OPS_DIR=/specular/ops
6 | L1_GETH_BIN=/usr/local/bin/geth
7 | SP_GETH_BIN=/usr/local/bin/geth
8 | SP_MAGI_BIN=/usr/local/bin/magi
9 | SIDECAR_BIN=/usr/local/bin/sidecar
10 | WAIT_DIR=/tmp
11 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/di/common_provider.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/google/wire"
5 |
6 | "github.com/specularL2/specular/services/sidecar/internal/service/config"
7 | )
8 |
9 | var CommonProvider = wire.NewSet( //nolint:gochecknoglobals
10 | config.NewLogger,
11 | config.NewCancelChannel,
12 | config.NewContext,
13 | )
14 |
--------------------------------------------------------------------------------
/config/kubernetes/.paths.env:
--------------------------------------------------------------------------------
1 | BIN_DIR=/usr/local/bin
2 | CONTRACTS_DIR=/specular/contracts
3 | CONFIG_DIR=/specular/config
4 | OPS_GENESIS_BIN=/usr/local/bin/genesis
5 | OPS_DIR=/specular/ops
6 | L1_GETH_BIN=/usr/local/bin/geth
7 | SP_GETH_BIN=/usr/local/bin/geth
8 | SP_MAGI_BIN=/usr/local/bin/magi
9 | SIDECAR_BIN=/usr/local/bin/sidecar
10 | WAIT_DIR=/specular/workspace
11 |
--------------------------------------------------------------------------------
/sbin/clean.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | SBIN=$(dirname $0)
3 | $SBIN/clean_sp_geth.sh
4 | $SBIN/clean_deployment.sh
5 |
6 | echo "Removing dotenv files..."
7 | rm -f .contracts.env
8 | rm -f .genesis.env
9 | rm -f .sp_geth.env
10 | rm -f .sp_magi.env
11 | rm -f .sidecar.env
12 | rm -f .paths.env
13 | echo "Done."
14 |
15 | echo "Removing $JWT_SECRET_PATH"
16 | rm -f $JWT_SECRET_PATH
17 |
--------------------------------------------------------------------------------
/config/e2e_test/.sp_magi.env:
--------------------------------------------------------------------------------
1 | NETWORK=./sp_rollup.json
2 | JWT_SECRET_PATH=./jwt_secret.txt
3 | L1_RPC_URL=http://127.0.0.1:8545
4 | L2_RPC_URL=http://127.0.0.1:4011
5 | L2_ENGINE_URL=http://127.0.0.1:4013
6 | RPC_PORT=10500
7 | SYNC_MODE=full
8 | DEVNET=true
9 | # Local sequencer flags
10 | SEQUENCER=true
11 | SEQUENCER_MAX_SAFE_LAG=200
12 | SEQUENCER_PK_FILE=./sequencer_pk.txt
13 | WATCHER_DELAY=50
14 |
--------------------------------------------------------------------------------
/config/local_devnet/.sp_magi.env:
--------------------------------------------------------------------------------
1 | NETWORK=./sp_rollup.json
2 | JWT_SECRET_PATH=./jwt_secret.txt
3 | L1_RPC_URL=http://127.0.0.1:8545
4 | L2_RPC_URL=http://127.0.0.1:4011
5 | L2_ENGINE_URL=http://127.0.0.1:4013
6 | RPC_PORT=10500
7 | SYNC_MODE=full
8 | DEVNET=true
9 | # Local sequencer flags
10 | SEQUENCER=true
11 | SEQUENCER_MAX_SAFE_LAG=200
12 | SEQUENCER_PK_FILE=./sequencer_pk.txt
13 | WATCHER_DELAY=50
14 |
--------------------------------------------------------------------------------
/config/kubernetes/.sidecar.env:
--------------------------------------------------------------------------------
1 | ROLLUP_CFG_PATH=./sp_rollup.json
2 | L1_ENDPOINT=ws://l1-geth:8545
3 | L2_ENDPOINT=http://sp-geth:4011
4 |
5 | DISSEMINATOR=true
6 | DISSEMINATOR_PK_PATH=./sequencer_pk.txt
7 | DISSEMINATOR_INTERVAL=6
8 | DISSEMINATOR_SUB_SAFETY_MARGIN=8
9 | DISSEMINATOR_TARGET_BATCH_SIZE=4096
10 | DISSEMINATOR_MAX_BATCH_SIZE=120000
11 | VALIDATOR=true
12 | VALIDATOR_PK_PATH=./validator_pk.txt
13 |
--------------------------------------------------------------------------------
/contracts/src/pre-deploy/L1FeeVault.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import {FeeVault} from "src/libraries/FeeVault.sol";
5 |
6 | /// @custom:proxied
7 | /// @custom:predeploy 0x2A00000000000000000000000000000000000020
8 | /// @title L1FeeVault
9 | /// @notice The L1FeeVault accumulates the L1 fee component of L2 transaction fees.
10 | contract L1FeeVault is FeeVault {}
11 |
--------------------------------------------------------------------------------
/config/kubernetes/.sp_magi.env:
--------------------------------------------------------------------------------
1 | NETWORK=./sp_rollup.json
2 | JWT_SECRET_PATH=./jwt_secret.txt
3 | L1_RPC_URL=http://192.168.160.2:8545
4 | L2_RPC_URL=http://192.168.160.3:4011
5 | L2_ENGINE_URL=http://192.168.160.3:4013
6 | RPC_PORT=10500
7 | SYNC_MODE=full
8 | DEVNET=true
9 | # Local sequencer flags
10 | SEQUENCER=true
11 | SEQUENCER_MAX_SAFE_LAG=200
12 | SEQUENCER_PK_FILE=./sequencer_pk.txt
13 | WATCHER_DELAY=4000
14 |
--------------------------------------------------------------------------------
/ops/README.md:
--------------------------------------------------------------------------------
1 | # Ops
2 |
3 | Specular chain operation tools.
4 |
5 | ## Bindings
6 |
7 | ```bash
8 | make -C bindings
9 | ```
10 |
11 | ## Genesis generation
12 |
13 | ```bash
14 | go run ./cmd/genesis/main.go \
15 | --genesis-config ./genesis-config.json \
16 | --out ./genesis.json \
17 | --l1-rpc-url http://localhost:8545 \
18 | --l1-block 0 \
19 | --export-hash ./genesis_hash.json
20 | ```
21 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/di/config_provider.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/google/wire"
5 |
6 | "github.com/specularL2/specular/services/sidecar/internal/service/config"
7 | )
8 |
9 | var ConfigProvider = wire.NewSet( //nolint:gochecknoglobals
10 | config.NewConfig,
11 | )
12 |
13 | var SystemConfigProvider = wire.NewSet( //nolint:gochecknoglobals
14 | config.NewSystemConfig,
15 | )
16 |
--------------------------------------------------------------------------------
/config/local_docker/.sp_magi.env:
--------------------------------------------------------------------------------
1 | NETWORK=./sp_rollup.json
2 | JWT_SECRET_PATH=./jwt_secret.txt
3 | L1_RPC_URL=http://192.168.160.2:8545
4 | L2_RPC_URL=http://192.168.160.3:4011
5 | L2_ENGINE_URL=http://192.168.160.3:4013
6 | RPC_PORT=10500
7 | SYNC_MODE=full
8 | DEVNET=true
9 | # Local sequencer flags
10 | SEQUENCER=true
11 | SEQUENCER_MAX_SAFE_LAG=200
12 | SEQUENCER_PK_FILE=./sequencer_pk.txt
13 | WATCHER_DELAY=4000
14 |
--------------------------------------------------------------------------------
/contracts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "ts-node": {
3 | "files": true
4 | },
5 | "compilerOptions": {
6 | "target": "es2020",
7 | "module": "commonjs",
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "strict": true,
12 | "skipLibCheck": true
13 | },
14 | "include": ["./hardhat.config.ts", "./scripts", "./deploy", "./test"]
15 | }
16 |
--------------------------------------------------------------------------------
/contracts/src/pre-deploy/L2BaseFeeVault.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import {FeeVault} from "src/libraries/FeeVault.sol";
5 |
6 | /// @custom:proxied
7 | /// @custom:predeploy 0x2A00000000000000000000000000000000000021
8 | /// @title L2BaseFeeVault
9 | /// @notice The L2BaseFeeVault accumulates the base fee component of L2 transaction fees.
10 | contract L2BaseFeeVault is FeeVault {}
11 |
--------------------------------------------------------------------------------
/config/example/blockscout/blockscout.env:
--------------------------------------------------------------------------------
1 | CHAIN_ID=13527
2 | SUBNETWORK=Specular Devnet
3 | LOGO=/images/specular_logo_horizontal.svg
4 | LOGO_FOOTER=/images/specular_logo_horizontal.svg
5 | COIN_NAME=ETH
6 | COIN=ETH
7 | SHOW_TESTNET_LABEL=true
8 | TESTNET_LABEL_TEXT=Devnet
9 | APPS_MENU=false
10 | BLOCKSCOUT_VERSION=v5.2.2-beta
11 | SECRET_KEY_BASE=56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN # Placeholder secret
12 |
--------------------------------------------------------------------------------
/packages/sdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "commonjs",
5 | "esModuleInterop": true,
6 | "resolveJsonModule": true,
7 | "moduleResolution": "node",
8 | "forceConsistentCasingInFileNames": true,
9 | "strict": true,
10 | "skipLibCheck": true,
11 | "declaration": true,
12 | "outDir": "./dist"
13 | },
14 | "include": ["./src", "scripts"]
15 | }
16 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/di/service_provider.go:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import (
4 | "github.com/google/wire"
5 |
6 | "github.com/specularL2/specular/services/sidecar/internal/sidecar/infra/services"
7 | )
8 |
9 | var DisseminatorProvider = wire.NewSet( //nolint:gochecknoglobals
10 | services.NewDisseminator,
11 | )
12 |
13 | var ValidatorProvider = wire.NewSet( //nolint:gochecknoglobals
14 | services.NewValidator,
15 | )
16 |
--------------------------------------------------------------------------------
/contracts/slither.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "filter_paths": "(lib/|@openzeppelin|hardhat|src/libraries/BytesLib.sol|test/)",
3 | "solc_remaps": [
4 | "@ensdomains/=node_modules/@ensdomains/",
5 | "@openzeppelin/=node_modules/@openzeppelin/",
6 | "ds-test/=lib/forge-std/lib/ds-test/src/",
7 | "forge-std/=lib/forge-std/src/",
8 | "hardhat-deploy/=node_modules/hardhat-deploy/",
9 | "hardhat/=node_modules/hardhat/"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/onboarding/README.md:
--------------------------------------------------------------------------------
1 | # Specular Onboarding Services
2 |
3 | This package implements the user onboarding services for the bridge.
4 |
5 | Note: this services is stateless.
6 |
7 | ## Install
8 |
9 | ```bash
10 | pnpm install
11 | ```
12 |
13 | ## Setup
14 |
15 | ```bash
16 | cp .env.example .env
17 | ```
18 |
19 | Fill in the `.env` file with the correct values.
20 |
21 | ## Run
22 |
23 | ```bash
24 | pnpm start
25 | ```
26 |
--------------------------------------------------------------------------------
/charts/specular/templates/sp-workspace-persistentvolume-claim.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolumeClaim
3 | metadata:
4 | name: {{ .Values.volume.efs.name }}
5 | spec:
6 | accessModes:
7 | - ReadWriteMany
8 | storageClassName: efs-sc
9 | resources:
10 | requests:
11 | storage: 5Gi
12 | ---
13 | kind: StorageClass
14 | apiVersion: storage.k8s.io/v1
15 | metadata:
16 | name: efs-sc
17 | provisioner: efs.csi.aws.com
18 |
--------------------------------------------------------------------------------
/contracts/scripts/e2e/addresses.ts:
--------------------------------------------------------------------------------
1 | export const l1OracleAddress = "0x2A00000000000000000000000000000000000010";
2 | export const l2PortalAddress = "0x2A00000000000000000000000000000000000011";
3 | export const l2StandardBridgeAddress =
4 | "0x2A00000000000000000000000000000000000012";
5 | export const l1FeeRecipientAddress =
6 | "0x2A00000000000000000000000000000000000020";
7 | export const l2BaseFeeRecipient = "0x2A00000000000000000000000000000000000021";
8 |
--------------------------------------------------------------------------------
/charts/specular/templates/config.yaml:
--------------------------------------------------------------------------------
1 |
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: specular-config
6 | data:
7 | {{- range $i, $value := .Values.configMaps }}
8 | {{ $i }}: |
9 | {{- range $ii, $item := $value }}
10 | {{ $item.name }}={{ $item.value }}
11 | {{- end }}
12 | {{- end }}
13 | {{- range $name, $value := .Values.jsonMaps }}
14 | {{ $name }}: |
15 | {{ $value | toJson }}
16 | {{- end }}
17 |
--------------------------------------------------------------------------------
/packages/onboarding/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "ts-node": {
3 | "files": true
4 | },
5 | "compilerOptions": {
6 | "target": "es2020",
7 | "module": "commonjs",
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "strict": true,
12 | "skipLibCheck": true,
13 | "outDir": "dist"
14 | },
15 | "include": ["./src/**/*.ts"],
16 | "files": ["./index.ts"]
17 | }
18 |
--------------------------------------------------------------------------------
/sbin/clean_sp_geth.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Check that a dotenv exists.
3 | GETH_ENV=".sp_geth.env"
4 | if ! test -f "$GETH_ENV"; then
5 | echo "expected dotenv at ./$GETH_ENV (does not exist); could not clean cwd."
6 | exit
7 | fi
8 | . $GETH_ENV
9 | echo "Removing sp-geth data dir $DATA_DIR"
10 | rm -rf $DATA_DIR
11 |
12 | if test -f .start_sp_geth.sh.lock; then
13 |
14 | echo "Removing docker lock file"
15 | L1_WAIT=$WAIT_DIR/.start_sp_geth.sh.lock
16 | fi
17 |
--------------------------------------------------------------------------------
/sbin/utils/crypto.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Generates a wallet.
4 | # Writes the address to stdout and private key to $1
5 | generate_wallet() {
6 | wallet=$(cast wallet new)
7 | address=$(echo "$wallet" | awk '/Address/ { print $2 }')
8 | priv_key=$(echo "$wallet" | awk '/Private key/ { print $3 }')
9 | guard_overwrite $1
10 | echo $priv_key | tr -d '\n' >"$1"
11 | echo "$address"
12 | }
13 |
14 | generate_jwt_secret() {
15 | openssl rand -hex 32
16 | }
17 |
--------------------------------------------------------------------------------
/charts/specular/templates/l1-geth-service.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.enabled.l1Geth -}}
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | creationTimestamp: null
6 | labels:
7 | io.specular.service: l1-geth
8 | name: l1-geth
9 | spec:
10 | ports:
11 | - name: "8545"
12 | port: 8545
13 | targetPort: 8545
14 | - name: "8546"
15 | port: 8546
16 | targetPort: 8546
17 | selector:
18 | io.specular.service: l1-geth
19 | {{- end -}}
20 |
--------------------------------------------------------------------------------
/packages/onboarding/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "es2021": true
5 | },
6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
7 | "overrides": [],
8 | "parser": "@typescript-eslint/parser",
9 | "parserOptions": {
10 | "ecmaVersion": "latest",
11 | "sourceType": "module"
12 | },
13 | "plugins": ["@typescript-eslint"],
14 | "rules": {},
15 | "ignorePatterns": ["dist", "node_modules"]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/sdk/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "es2021": true
5 | },
6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
7 | "overrides": [],
8 | "parser": "@typescript-eslint/parser",
9 | "parserOptions": {
10 | "ecmaVersion": "latest",
11 | "sourceType": "module"
12 | },
13 | "plugins": ["@typescript-eslint"],
14 | "ignorePatterns": ["dist", "src/types/contracts"],
15 | "rules": {}
16 | }
17 |
--------------------------------------------------------------------------------
/config/e2e_test/.sidecar.env:
--------------------------------------------------------------------------------
1 | ROLLUP_CFG_PATH=./sp_rollup.json
2 | L1_ENDPOINT=ws://127.0.0.1:8545
3 | L2_ENDPOINT=ws://127.0.0.1:4012
4 |
5 | DISSEMINATOR=true
6 | DISSEMINATOR_PK_PATH=./sequencer_pk.txt
7 | DISSEMINATOR_INTERVAL=2
8 | DISSEMINATOR_SUB_SAFETY_MARGIN=8
9 | DISSEMINATOR_TARGET_BATCH_SIZE=1024
10 | DISSEMINATOR_MAX_BATCH_SIZE=4096
11 | DISSEMINATOR_MAX_SAFE_LAG=200
12 | DISSEMINATOR_MAX_SAFE_LAG_DELTA=40
13 | VALIDATOR=true
14 | VALIDATOR_PK_PATH=./validator_pk.txt
15 |
--------------------------------------------------------------------------------
/config/local_docker/.sidecar.env:
--------------------------------------------------------------------------------
1 | ROLLUP_CFG_PATH=./sp_rollup.json
2 | L1_ENDPOINT=ws://l1-geth:8545
3 | L2_ENDPOINT=http://sp-geth:4011
4 |
5 | DISSEMINATOR=true
6 | DISSEMINATOR_PK_PATH=./sequencer_pk.txt
7 | DISSEMINATOR_INTERVAL=6
8 | DISSEMINATOR_SUB_SAFETY_MARGIN=8
9 | DISSEMINATOR_TARGET_BATCH_SIZE=4096
10 | DISSEMINATOR_MAX_BATCH_SIZE=120000
11 | DISSEMINATOR_MAX_SAFE_LAG=200
12 | DISSEMINATOR_MAX_SAFE_LAG_DELTA=40
13 | VALIDATOR=true
14 | VALIDATOR_PK_PATH=./validator_pk.txt
15 |
--------------------------------------------------------------------------------
/charts/specular/templates/sp-geth-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | creationTimestamp: null
5 | labels:
6 | io.specular.service: sp-geth
7 | name: sp-geth
8 | spec:
9 | ports:
10 | - name: "4011"
11 | port: 4011
12 | targetPort: 4011
13 | - name: "4012"
14 | port: 4012
15 | targetPort: 4012
16 | - name: "4013"
17 | port: 4013
18 | targetPort: 4013
19 | selector:
20 | io.specular.service: sp-geth
21 |
--------------------------------------------------------------------------------
/config/local_devnet/.sidecar.env:
--------------------------------------------------------------------------------
1 | ROLLUP_CFG_PATH=./sp_rollup.json
2 | L1_ENDPOINT=ws://127.0.0.1:8545
3 | L2_ENDPOINT=ws://127.0.0.1:4012
4 |
5 | DISSEMINATOR=true
6 | DISSEMINATOR_PK_PATH=./sequencer_pk.txt
7 | DISSEMINATOR_INTERVAL=2
8 | DISSEMINATOR_SUB_SAFETY_MARGIN=8
9 | DISSEMINATOR_TARGET_BATCH_SIZE=4096
10 | DISSEMINATOR_MAX_BATCH_SIZE=120000
11 | DISSEMINATOR_MAX_SAFE_LAG=200
12 | DISSEMINATOR_MAX_SAFE_LAG_DELTA=40
13 | VALIDATOR=true
14 | VALIDATOR_PK_PATH=./validator_pk.txt
15 |
--------------------------------------------------------------------------------
/sbin/generate_jwt_secret.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | SBIN=$(dirname "$(readlink -f "$0")")
3 | SBIN="$(
4 | cd "$SBIN"
5 | pwd
6 | )"
7 | . $SBIN/utils/utils.sh
8 | . $SBIN/utils/crypto.sh
9 |
10 | ROOT_DIR=$SBIN/..
11 | JWT=$(generate_jwt_secret)
12 | # Write to sp-magi's expected JWT secret path.
13 | reqdotenv "sp_magi" ".sp_magi.env"
14 | echo $JWT >$JWT_SECRET_PATH
15 | # Write to sp-geth's expected JWT secret path.
16 | reqdotenv "sp_magi" ".sp_geth.env"
17 | echo $JWT >$JWT_SECRET_PATH
18 |
--------------------------------------------------------------------------------
/contracts/src/test/TestToken.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.9;
3 |
4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5 | import "@openzeppelin/contracts/access/Ownable.sol";
6 |
7 | contract TestToken is ERC20, Ownable {
8 | constructor() ERC20("TestToken", "TT") {
9 | _mint(msg.sender, 100 * 10 ** decimals());
10 | }
11 |
12 | function mint(address to, uint256 amount) public onlyOwner {
13 | _mint(to, amount);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/actions/foundry/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Build foundry'
2 |
3 | runs:
4 | using: "composite"
5 | steps:
6 | - name: Cache Foundry toolchain
7 | uses: actions/cache@v2
8 | with:
9 | path: ~/.foundry
10 | key: ${{ runner.os }}-foundry-${{ hashFiles('**/foundry.toml') }}
11 | restore-keys: |
12 | ${{ runner.os }}-foundry-
13 |
14 | - name: Install Foundry
15 | uses: foundry-rs/foundry-toolchain@v1
16 | with:
17 | version: nightly
18 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "contracts/lib/forge-std"]
2 | path = contracts/lib/forge-std
3 | url = https://github.com/foundry-rs/forge-std
4 | [submodule "blockscout"]
5 | path = blockscout
6 | url = https://github.com/SpecularL2/blockscout
7 | [submodule "services/el_clients/go-ethereum"]
8 | path = services/el_clients/go-ethereum
9 | url = https://github.com/specularL2/go-ethereum
10 | [submodule "services/cl_clients/magi"]
11 | path = services/cl_clients/magi
12 | url = https://github.com/specularl2/magi
13 |
--------------------------------------------------------------------------------
/contracts/deploy/l1/01_deploy_Verifier.ts:
--------------------------------------------------------------------------------
1 | import { HardhatRuntimeEnvironment } from "hardhat/types";
2 | import { DeployFunction } from "hardhat-deploy/types";
3 |
4 | import { deployUUPSProxiedContract } from "../utils";
5 |
6 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
7 | const { deployer } = await hre.getNamedAccounts();
8 | await deployUUPSProxiedContract(hre, deployer, "Verifier", []);
9 | };
10 |
11 | export default func;
12 | func.tags = ["Verifier", "L1", "Stage0"];
13 |
--------------------------------------------------------------------------------
/contracts/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = 'src'
3 | out = 'out'
4 | libs = ['node_modules', 'lib']
5 | remappings = [
6 | '@openzeppelin/=node_modules/@openzeppelin/',
7 | 'hardhat-deploy/=node_modules/hardhat-deploy/',
8 | 'hardhat/=node_modules/hardhat/',
9 | ]
10 | test = 'test'
11 | cache_path = 'forge-cache'
12 | allow_paths = [
13 | '../node_modules',
14 | ]
15 |
16 | [fmt]
17 | line_length = 120
18 |
19 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config
20 |
--------------------------------------------------------------------------------
/bindings-go/artifacts.json:
--------------------------------------------------------------------------------
1 | [
2 | "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol",
3 | "src/bridge/L1Oracle.sol",
4 | "src/bridge/L2Portal.sol",
5 | "src/bridge/L2StandardBridge.sol",
6 | "src/pre-deploy/UUPSPlaceholder.sol",
7 | "src/pre-deploy/L2BaseFeeVault.sol",
8 | "src/pre-deploy/L1FeeVault.sol",
9 | "src/pre-deploy/MintableERC20Factory.sol",
10 | "src/bridge/L1Oracle.sol",
11 | "src/challenge/IChallenge.sol",
12 | "src/IRollup.sol",
13 | "src/ISequencerInbox.sol"
14 | ]
15 |
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "specular-monorepo",
3 | "version": "0.0.1",
4 | "private": true,
5 | "description": "",
6 | "scripts": {
7 | "prepare": "husky install ./.husky"
8 | },
9 | "keywords": [],
10 | "author": "Specular contributors",
11 | "license": "Apache-2.0",
12 | "devDependencies": {
13 | "@changesets/cli": "^2.26.2",
14 | "@pnpm/make-dedicated-lockfile": "^0.5.0",
15 | "@types/node": "^18.13.0",
16 | "husky": "^8.0.1",
17 | "ts-node": "^10.9.1",
18 | "typescript": "^4.9.3"
19 | }
20 | }
--------------------------------------------------------------------------------
/config/e2e_test/.paths.env:
--------------------------------------------------------------------------------
1 | CONTRACTS_DIR=$ROOT_DIR/contracts
2 | CONFIG_DIR=$ROOT_DIR/config
3 | OPS_DIR=$ROOT_DIR/ops
4 | GETH_DIR=$ROOT_DIR/services/el_clients/go-ethereum
5 | MAGI_DIR=$ROOT_DIR/services/cl_clients/magi
6 | SIDECAR_DIR=$ROOT_DIR/services/sidecar
7 | # Define binaries
8 | OPS_GENESIS_BIN=$OPS_DIR/build/bin/genesis
9 | L1_GETH_BIN=$GETH_DIR/build/bin/geth # TODO: use l1, not sp?
10 | SP_GETH_BIN=$GETH_DIR/build/bin/geth
11 | SP_MAGI_BIN=$MAGI_DIR/target/debug/magi
12 | SIDECAR_BIN=$SIDECAR_DIR/build/bin/sidecar
13 | WAIT_DIR=/tmp
14 |
--------------------------------------------------------------------------------
/contracts/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "node": true,
5 | "es2021": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/recommended"
10 | ],
11 | "overrides": [
12 | ],
13 | "parser": "@typescript-eslint/parser",
14 | "parserOptions": {
15 | "ecmaVersion": "latest",
16 | "sourceType": "module"
17 | },
18 | "plugins": [
19 | "@typescript-eslint"
20 | ],
21 | "rules": {
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/config/example/.contracts.env:
--------------------------------------------------------------------------------
1 | # This dotenv contains the env variables for configuring contract deployment.
2 | # Will be copied automatically into the `contracts/` directory during deployment.
3 | # This dotenv is required only if you want to deploy the protocol yourself,
4 | # (both in prod, or as a local devnet for testing).
5 |
6 | # Node addresses for contract deployment.
7 | SEQUENCER_ADDRESS=0x0000000000000000000000000000000000000000
8 | VALIDATOR_ADDRESS=0x0000000000000000000000000000000000000000
9 | DEPLOYER_ADDRESS=0x0000000000000000000000000000000000000000
--------------------------------------------------------------------------------
/config/local_devnet/.paths.env:
--------------------------------------------------------------------------------
1 | CONTRACTS_DIR=$ROOT_DIR/contracts
2 | CONFIG_DIR=$ROOT_DIR/config
3 | OPS_DIR=$ROOT_DIR/ops
4 | GETH_DIR=$ROOT_DIR/services/el_clients/go-ethereum
5 | MAGI_DIR=$ROOT_DIR/services/cl_clients/magi
6 | SIDECAR_DIR=$ROOT_DIR/services/sidecar
7 | # Define binaries
8 | OPS_GENESIS_BIN=$OPS_DIR/build/bin/genesis
9 | L1_GETH_BIN=$GETH_DIR/build/bin/geth # TODO: use l1, not sp?
10 | SP_GETH_BIN=$GETH_DIR/build/bin/geth
11 | SP_MAGI_BIN=$MAGI_DIR/target/debug/magi
12 | SIDECAR_BIN=$SIDECAR_DIR/build/bin/sidecar
13 | WAIT_DIR=/tmp
14 |
--------------------------------------------------------------------------------
/charts/specular/templates/sp-workspace-persistentvolume.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolume
3 | metadata:
4 | creationTimestamp: null
5 | labels:
6 | io.specular.service: {{ .Values.volume.efs.name }}
7 | name: {{ .Values.volume.efs.name }}
8 | spec:
9 | capacity:
10 | storage: 5Gi
11 | volumeMode: Filesystem
12 | accessModes:
13 | - ReadWriteMany
14 | persistentVolumeReclaimPolicy: Retain
15 | storageClassName: efs-sc
16 | csi:
17 | driver: efs.csi.aws.com
18 | volumeHandle: {{ .Values.efsHandle }}
19 | status: {}
20 |
--------------------------------------------------------------------------------
/contracts/src/libraries/Types.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.9;
3 |
4 | /**
5 | * @title Types
6 | * @notice Contains various types used throughout the Specular contract system.
7 | */
8 | library Types {
9 | /**
10 | * @notice Struct representing a cross domain message.
11 | */
12 | struct CrossDomainMessage {
13 | uint256 version;
14 | uint256 nonce;
15 | address sender;
16 | address target;
17 | uint256 value;
18 | uint256 gasLimit;
19 | bytes data;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/docs/contracts.md:
--------------------------------------------------------------------------------
1 | # Protocol contracts
2 | Protocol contracts are located in `contracts/`.
3 | These are the L1 contracts and L2 pre-deploys of Specular.
4 |
5 | The package is configured to use both Hardhat (for deployment) and Founrdry (for testing).
6 | See `hardhat.config.js` for the full configuration and `deploy/deploy.js` for how contracts are deployed.
7 |
8 | ### Run tests
9 |
10 | ```sh
11 | forge test
12 | ```
13 |
14 | ### Local slither check
15 | Install [`slither`](https://github.com/crytic/slither):
16 | ```sh
17 | pip3 install slither-analyzer
18 | ```
19 |
20 | Run slither:
21 | ```sh
22 | slither .
23 | ```
24 |
--------------------------------------------------------------------------------
/services/sidecar/Dockerfile:
--------------------------------------------------------------------------------
1 | # install dependencies and build sidecar binary
2 | FROM node:16-alpine3.17 as builder
3 |
4 | RUN apk add --no-cache git go python3 make g++ musl-dev linux-headers
5 | RUN corepack enable
6 |
7 | WORKDIR /specular
8 | COPY . /specular
9 |
10 | # frozen lockfile is automatically enabled in CI environments
11 | RUN pnpm install
12 | RUN make sidecar
13 |
14 | # create container with just the binary
15 | FROM alpine:3.17
16 |
17 | WORKDIR /specular
18 |
19 | RUN apk add --no-cache ca-certificates bash
20 | COPY --from=builder /specular/services/sidecar/build/bin/sidecar /usr/local/bin/
21 |
22 | EXPOSE 8545 8546
23 |
--------------------------------------------------------------------------------
/contracts/deploy/l1/00_deploy_SequencerInbox.ts:
--------------------------------------------------------------------------------
1 | import { HardhatRuntimeEnvironment } from "hardhat/types";
2 | import { DeployFunction } from "hardhat-deploy/types";
3 |
4 | import { deployUUPSProxiedContract } from "../utils";
5 |
6 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
7 | const { deployer } = await hre.getNamedAccounts();
8 | const args = [process.env.SEQUENCER_ADDRESS];
9 | console.log("Deploying SequencerInbox with args:", args);
10 | await deployUUPSProxiedContract(hre, deployer, "SequencerInbox", args);
11 | };
12 |
13 | export default func;
14 | func.tags = ["SequencerInbox", "L1", "Stage0"];
15 |
--------------------------------------------------------------------------------
/docker/e2e.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM 792926601177.dkr.ecr.us-east-2.amazonaws.com/specular-platform:build-latest
2 |
3 | ENV FOUNDRY_VERSION="nightly"
4 | ENV FOUNDRY_TAR="foundry_nightly_linux_amd64.tar.gz"
5 |
6 | WORKDIR /tmp
7 | RUN wget https://github.com/foundry-rs/foundry/releases/download/$FOUNDRY_VERSION/$FOUNDRY_TAR && \
8 | tar xzvf $FOUNDRY_TAR && \
9 | mv cast /usr/local/bin
10 |
11 | WORKDIR /specular
12 | ADD . /specular
13 |
14 |
15 | # frozen lockfile is automatically enabled in CI environments
16 | RUN pnpm install
17 |
18 | ENV RUST_BACKTRACE=full
19 | RUN make
20 |
21 | # TODO: what ports should be exposed?
22 | EXPOSE 8545 8546
23 |
--------------------------------------------------------------------------------
/services/sidecar/rollup/rpc/eth/txmgr/metrics/noop.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import "github.com/ethereum/go-ethereum/core/types"
4 |
5 | // TODO: prometheus-based metrics type
6 | type NoopTxMetrics struct{}
7 |
8 | func (*NoopTxMetrics) RecordNonce(uint64) {}
9 | func (*NoopTxMetrics) RecordPendingTx(int64) {}
10 | func (*NoopTxMetrics) RecordGasBumpCount(int) {}
11 | func (*NoopTxMetrics) RecordTxConfirmationLatency(int64) {}
12 | func (*NoopTxMetrics) TxConfirmed(*types.Receipt) {}
13 | func (*NoopTxMetrics) TxPublished(string) {}
14 | func (*NoopTxMetrics) RPCError() {}
15 |
--------------------------------------------------------------------------------
/.github/actions/node/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Build node dependencies'
2 |
3 | runs:
4 | using: "composite"
5 | steps:
6 | - name: Install Node.js
7 | uses: actions/setup-node@v3
8 | with:
9 | node-version: 16
10 |
11 | - uses: pnpm/action-setup@v2
12 | name: Install pnpm
13 | id: pnpm-install
14 | with:
15 | version: 8
16 | run_install: false
17 |
18 | - name: Get pnpm store directory
19 | id: pnpm-cache
20 | shell: bash
21 | run: |
22 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
23 |
24 | - name: Install dependencies
25 | shell: bash
26 | run: pnpm install
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/contracts/scripts/config/utils.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 |
3 | export function parseFlag(flag: string, defaultValue?: string): string {
4 | const flagIndex = process.argv.indexOf(flag);
5 | let value: string | undefined;
6 |
7 | if (flagIndex < 0) {
8 | value = defaultValue;
9 | } else {
10 | value = process.argv[flagIndex + 1];
11 | }
12 |
13 | if (value === undefined) throw Error(`no value set for "${flag}"`);
14 | return value;
15 | }
16 |
17 | export function numberStrToPaddedHex(numStr: string, length: number): string {
18 | return ethers.utils.hexZeroPad(
19 | ethers.BigNumber.from(numStr).toHexString(),
20 | length,
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/packages/sdk/README.md:
--------------------------------------------------------------------------------
1 | # Specular SDK
2 |
3 | ## What is Specular SDK?
4 |
5 | Software Development Kit for interacting with the Specular.
6 |
7 | ### What is the SDK for?
8 |
9 | The SDK helps you use the following functionalities and provides a seamless experience for getting involved with Specular as a developer.
10 |
11 | - Deposit native ETH and ERC20 tokens from an L1 network (initially Sepolia) to Specular Testnet
12 | - Withdraw native ETH and ERC20 tokens from Specular Testnet to an L1 network(initially Sepolia)
13 | - Check Specular Testnet balances
14 |
15 | You can clone the repo and play around with the example scripts.
16 |
17 | More about [Specular](https://docs.specular.network/).
18 |
--------------------------------------------------------------------------------
/bindings-go/bindings/IRollup_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const IRollupStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
13 |
14 | var IRollupStorageLayout = new(solc.StorageLayout)
15 |
16 | var IRollupDeployedBin = "0x"
17 | func init() {
18 | if err := json.Unmarshal([]byte(IRollupStorageLayoutJSON), IRollupStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["IRollup"] = IRollupStorageLayout
23 | deployedBytecodes["IRollup"] = IRollupDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/config/example/.paths.env:
--------------------------------------------------------------------------------
1 | # Define the directory structure and binaries relative to a root directory
2 | # The config below reflects the repository structure.
3 |
4 | # Define directories
5 | CONTRACTS_DIR=$ROOT_DIR/contracts
6 | CONFIG_DIR=$ROOT_DIR/config
7 | OPS_DIR=$ROOT_DIR/ops
8 | GETH_DIR=$ROOT_DIR/services/el_clients/go-ethereum
9 | MAGI_DIR=$ROOT_DIR/services/cl_clients/magi
10 | SIDECAR_DIR=$ROOT_DIR/services/sidecar
11 |
12 | # Define binaries
13 | OPS_GENESIS_BIN=$OPS_DIR/build/bin/genesis
14 | L1_GETH_BIN=$GETH_DIR/build/bin/geth # TODO: use l1, not sp?
15 | SP_GETH_BIN=$GETH_DIR/build/bin/geth
16 | SP_MAGI_BIN=$MAGI_DIR/target/debug/magi
17 | SIDECAR_BIN=$SIDECAR_DIR/build/bin/sidecar
18 | WAIT_DIR=/tmp
19 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/config/logger.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/specularL2/specular/services/sidecar/rollup/services"
7 | "github.com/specularL2/specular/services/sidecar/utils/log"
8 | )
9 |
10 | func NewLogger(systemCfg *services.SystemConfig) log.Logger {
11 | baseLog := log.New()
12 | gLogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
13 | gLogger.Verbosity(systemCfg.Verbosity)
14 | baseLog.SetHandler(gLogger)
15 | // TODO: refactor is required to the "old" code to pass down the logger instance from the provider,
16 | // for now let's keep the global setting of the handler.
17 | log.Root().SetHandler(gLogger)
18 | return baseLog
19 | }
20 |
--------------------------------------------------------------------------------
/bindings-go/bindings/IChallenge_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const IChallengeStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
13 |
14 | var IChallengeStorageLayout = new(solc.StorageLayout)
15 |
16 | var IChallengeDeployedBin = "0x"
17 | func init() {
18 | if err := json.Unmarshal([]byte(IChallengeStorageLayoutJSON), IChallengeStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["IChallenge"] = IChallengeStorageLayout
23 | deployedBytecodes["IChallenge"] = IChallengeDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/docs/architecture.md:
--------------------------------------------------------------------------------
1 | ## Directory Structure
2 |
3 |
4 | ├── services: L2 services
5 | │ ├── cl_clients: Consensus-layer clients
6 | │ ├── el_clients: Execution-layer clients
7 | │ │ └── go-ethereum: Minimally modified geth fork
8 | │ └── sidecar: Sidecar services
9 | ├── contracts: L1 and L2 contracts
10 | └── lib: Libraries used in L2 EL Clients
11 | └── el_golang_lib: Library for golang EL clients
12 |
13 |
14 | **TODO**: This document is under-construction.
15 |
--------------------------------------------------------------------------------
/contracts/scripts/bridge/confirm_oracle.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "hardhat";
2 | import { getSignersAndContracts } from "../e2e/utils";
3 |
4 | async function main() {
5 | const { l1Provider, l1Oracle } = await getSignersAndContracts();
6 |
7 | const blockNumber = await l1Provider.getBlockNumber();
8 | const rawBlock = await l1Provider.send("eth_getBlockByNumber", [
9 | ethers.utils.hexValue(blockNumber),
10 | false, // We only want the block header
11 | ]);
12 | const stateRoot = l1Provider.formatter.hash(rawBlock.stateRoot);
13 | await l1Oracle.setL1OracleValues(blockNumber, stateRoot, 0);
14 | }
15 |
16 | main()
17 | .then(() => process.exit(0))
18 | .catch((error) => {
19 | console.error(error);
20 | process.exit(1);
21 | });
22 |
--------------------------------------------------------------------------------
/bindings-go/bindings/ISymChallenge_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const ISymChallengeStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
13 |
14 | var ISymChallengeStorageLayout = new(solc.StorageLayout)
15 |
16 | var ISymChallengeDeployedBin = "0x"
17 | func init() {
18 | if err := json.Unmarshal([]byte(ISymChallengeStorageLayoutJSON), ISymChallengeStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["ISymChallenge"] = ISymChallengeStorageLayout
23 | deployedBytecodes["ISymChallenge"] = ISymChallengeDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/contracts/deploy/l1/03_deploy_L1Portal.ts:
--------------------------------------------------------------------------------
1 | import { HardhatRuntimeEnvironment } from "hardhat/types";
2 | import { DeployFunction } from "hardhat-deploy/types";
3 | import { deployUUPSProxiedContract, getProxyName } from "../utils";
4 |
5 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
6 | const { deployments, getNamedAccounts } = hre;
7 | const { deployer } = await getNamedAccounts();
8 | const rollupProxyAddress = (await deployments.get(getProxyName("Rollup")))
9 | .address;
10 | const args = [rollupProxyAddress];
11 | console.log("Deploying L1Portal with args:", args);
12 | await deployUUPSProxiedContract(hre, deployer, "L1Portal", args);
13 | };
14 |
15 | export default func;
16 | func.tags = ["L1Portal", "L1", "Stage0"];
17 |
--------------------------------------------------------------------------------
/bindings-go/bindings/IAsymChallenge_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const IAsymChallengeStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
13 |
14 | var IAsymChallengeStorageLayout = new(solc.StorageLayout)
15 |
16 | var IAsymChallengeDeployedBin = "0x"
17 | func init() {
18 | if err := json.Unmarshal([]byte(IAsymChallengeStorageLayoutJSON), IAsymChallengeStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["IAsymChallenge"] = IAsymChallengeStorageLayout
23 | deployedBytecodes["IAsymChallenge"] = IAsymChallengeDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/config/local_devnet/genesis_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "l2ChainID": 13527,
3 | "l2GenesisBlockNonce": "0x0",
4 | "l2GenesisBlockGasLimit": "0x0",
5 | "l2GenesisBlockDifficulty": "0x0",
6 | "l2GenesisBlockNumber": "0x0",
7 | "l2GenesisBlockGasUsed": "0x0",
8 | "l2GenesisBlockBaseFeePerGas": "0x1",
9 | "l2GenesisBlockExtraData": "0x",
10 | "l2PredeployOwner": "0x000000000000000000000000000000000000dEaD",
11 | "l2FeesWithdrawalAddress": "0x000000000000000000000000000000000000dEaD",
12 | "l2FeesMinWithdrwalAmount": "0x0",
13 | "l1FeeOverhead": "0x1",
14 | "l1FeeScalar": "0x1",
15 | "alloc": {
16 | "2a00000000000000000000000000000000000011": {
17 | "balance": "1000000000000000000000000000"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/bindings-go/bindings/ISequencerInbox_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const ISequencerInboxStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
13 |
14 | var ISequencerInboxStorageLayout = new(solc.StorageLayout)
15 |
16 | var ISequencerInboxDeployedBin = "0x"
17 | func init() {
18 | if err := json.Unmarshal([]byte(ISequencerInboxStorageLayoutJSON), ISequencerInboxStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["ISequencerInbox"] = ISequencerInboxStorageLayout
23 | deployedBytecodes["ISequencerInbox"] = ISequencerInboxDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/contracts/deploy/l1/04_deploy_L1StandardBridge.ts:
--------------------------------------------------------------------------------
1 | import { HardhatRuntimeEnvironment } from "hardhat/types";
2 | import { DeployFunction } from "hardhat-deploy/types";
3 | import { deployUUPSProxiedContract, getProxyName } from "../utils";
4 |
5 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
6 | const { deployments, getNamedAccounts } = hre;
7 | const { deployer } = await getNamedAccounts();
8 | const l1PortalProxyAddress = (await deployments.get(getProxyName("L1Portal")))
9 | .address;
10 | const args = [l1PortalProxyAddress];
11 | console.log("Deploying L1StandardBridge with args:", args);
12 | await deployUUPSProxiedContract(hre, deployer, "L1StandardBridge", args);
13 | };
14 |
15 | export default func;
16 | func.tags = ["L1StandardBridge", "L1", "Stage0"];
17 |
--------------------------------------------------------------------------------
/services/sidecar/Makefile:
--------------------------------------------------------------------------------
1 | GO_CMD=go
2 | GO_BUILD=$(GO_CMD) build
3 | CMD_PATH=./cmd/sidecar/main.go
4 |
5 | .PHONY: help # Help - list of targets with descriptions
6 | help:
7 | @echo ''
8 | @echo 'Usage:'
9 | @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}'
10 | @echo ''
11 | @echo 'Targets:'
12 | @grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/ \1\t\2/' | expand -t20
13 |
14 | .PHONY: wire-generate # Generate Wire bindings
15 | wire-generate:
16 | cd internal/service/di ;\
17 | wire
18 |
19 | .PHONY: lint-test # Run lint-tests
20 | lint-test:
21 | go mod tidy
22 | goimports -local github.com/specularL2/specular/services/sidecar -w .
23 | go fmt ./...
24 | @golangci-lint -v run ./...
25 | @test -z "$$(golangci-lint run ./...)"
26 |
27 | .PHONY: lint # Run lint-tests (alias)
28 | lint: lint-test
29 |
--------------------------------------------------------------------------------
/services/sidecar/cmd/sidecar/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "log"
7 | "os"
8 |
9 | "github.com/specularL2/specular/services/sidecar/internal/service/di"
10 | )
11 |
12 | func main() {
13 | application, _, err := di.SetupApplication()
14 | if err != nil {
15 | log.Fatalf("failed to setup application %s", err)
16 | }
17 |
18 | exitCode := 0
19 | defer func() { os.Exit(exitCode) }()
20 |
21 | go func() {
22 | <-application.GetContext().Done()
23 |
24 | application.GetLogger().Info("application context canceled, cleaning up")
25 | application.ShutdownAndCleanup(exitCode)
26 | }()
27 |
28 | if err := application.Run(); err != nil {
29 | if !errors.Is(err, context.Canceled) {
30 | application.GetLogger().Crit("application failed")
31 | exitCode = 1
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/bindings-go/bindings/IChallengeResultReceiver_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const IChallengeResultReceiverStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
13 |
14 | var IChallengeResultReceiverStorageLayout = new(solc.StorageLayout)
15 |
16 | var IChallengeResultReceiverDeployedBin = "0x"
17 | func init() {
18 | if err := json.Unmarshal([]byte(IChallengeResultReceiverStorageLayoutJSON), IChallengeResultReceiverStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["IChallengeResultReceiver"] = IChallengeResultReceiverStorageLayout
23 | deployedBytecodes["IChallengeResultReceiver"] = IChallengeResultReceiverDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/lib/el_golang_lib/hook/fee_hook_test.go:
--------------------------------------------------------------------------------
1 | package hook
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "math/big"
7 | "testing"
8 | )
9 |
10 | func TestScaleBigInt(t *testing.T) {
11 | largeResult, _ := new(big.Int).SetString("12912720851596686130", 10)
12 |
13 | var tests = []struct {
14 | num *big.Int
15 | scalar float64
16 | want *big.Int
17 | }{
18 | {big.NewInt(10), 1, big.NewInt(10)},
19 | {big.NewInt(1), 1.2, big.NewInt(2)},
20 | {big.NewInt(10), 9.999, big.NewInt(100)},
21 | {big.NewInt(math.MaxInt64), 1, big.NewInt(math.MaxInt64)},
22 | {big.NewInt(math.MaxInt64), 1.4, largeResult},
23 | }
24 |
25 | for _, tt := range tests {
26 | testname := fmt.Sprintf("%s,%f", tt.num.String(), tt.scalar)
27 | t.Run(testname, func(t *testing.T) {
28 | ans := ScaleBigInt(tt.num, tt.scalar)
29 | if ans.Cmp(tt.want) != 0 {
30 | t.Errorf("got %d, want %d", ans, tt.want)
31 | }
32 | })
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/di/inject.go:
--------------------------------------------------------------------------------
1 | //go:build wireinject
2 | // +build wireinject
3 |
4 | package di
5 |
6 | import (
7 | "github.com/google/wire"
8 |
9 | "github.com/specularL2/specular/services/sidecar/internal/service/config"
10 | )
11 |
12 | func SetupApplication() (*Application, func(), error) {
13 | panic(wire.Build(wire.NewSet(
14 | CommonProvider,
15 | ConfigProvider,
16 | SystemConfigProvider,
17 | L1StateProvider,
18 | DisseminatorProvider,
19 | ValidatorProvider,
20 | wire.Struct(new(Application), "*"))),
21 | )
22 | }
23 |
24 | func SetupApplicationForIntegrationTests(cfg *config.Config) (*TestApplication, func(), error) {
25 | panic(wire.Build(wire.NewSet(
26 | CommonProvider,
27 | SystemConfigProvider,
28 | L1StateProvider,
29 | DisseminatorProvider,
30 | ValidatorProvider,
31 | wire.Struct(new(Application), "*"),
32 | wire.Struct(new(TestApplication), "*"))),
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/config/context.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "context"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/specularL2/specular/services/sidecar/utils/log"
10 | )
11 |
12 | type CancelChannel chan struct{}
13 |
14 | func NewCancelChannel() CancelChannel {
15 | return make(chan struct{}, 1)
16 | }
17 |
18 | func NewContext(log log.Logger, termination CancelChannel) context.Context {
19 | ctx, cancel := context.WithCancel(context.Background())
20 |
21 | quit := make(chan os.Signal, 1)
22 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
23 |
24 | go func() {
25 | for {
26 | select {
27 | case sig := <-quit:
28 | log.Info("os signal - shutting down", "signal", sig)
29 | cancel()
30 | return
31 | case <-termination:
32 | log.Info("term signal - shutting down")
33 | cancel()
34 | return
35 | }
36 | }
37 | }()
38 |
39 | return ctx
40 | }
41 |
--------------------------------------------------------------------------------
/charts/specular/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: specular-ingress
5 | namespace: specular
6 | annotations:
7 |
8 | # nginx.ingress.kubernetes.io/ssl-redirect: "true"
9 | # nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
10 | nginx.ingress.kubernetes.io/rewrite-target: /
11 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
12 | spec:
13 | ingressClassName: nginx
14 | rules:
15 | - host: {{ .Values.environment }}.specular.network
16 | http:
17 | paths:
18 | - path: /
19 | pathType: Prefix
20 | backend:
21 | service:
22 | name: sp-geth
23 | port:
24 | number: 4011
25 | - path: /ws
26 | pathType: Prefix
27 | backend:
28 | service:
29 | name: sp-geth
30 | port:
31 | number: 4012
32 |
--------------------------------------------------------------------------------
/services/sidecar/rollup/rpc/eth/tag.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | type BlockTag string
4 |
5 | // Block tags (used both for L1 and L2)
6 | // https://ethereum.github.io/execution-apis/api-documentation/
7 | const (
8 | // - L1: The most recent block in the canonical chain observed by the client,
9 | // this block may be re-orged out of the canonical chain even under healthy/normal conditions
10 | // - L2: Local chain head (unconfirmed on L1)
11 | Latest BlockTag = "latest"
12 | // - L1: The most recent block that is safe from re-orgs under honest majority and certain synchronicity assumptions
13 | // - L2: Derived chain tip from (not-necessarily safe) L1 data
14 | Safe BlockTag = "safe"
15 | // - L1: The most recent crypto-economically secure block,
16 | // cannot be re-orged outside of manual intervention driven by community coordination
17 | // - L2: Derived chain tip from finalized L1 data
18 | Finalized BlockTag = "finalized"
19 | )
20 |
--------------------------------------------------------------------------------
/services/sidecar/utils/log/root.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ethereum/go-ethereum/log"
7 | )
8 |
9 | func Root() log.Logger { return &logger{log.Root()} }
10 |
11 | func Trace(msg string, args ...interface{}) { log.Trace(getLogPrefix()+msg, args...) }
12 | func Debug(msg string, args ...interface{}) { log.Debug(getLogPrefix()+msg, args...) }
13 | func Info(msg string, args ...interface{}) { log.Info(getLogPrefix()+msg, args...) }
14 | func Warn(msg string, args ...interface{}) { log.Warn(getLogPrefix()+msg, args...) }
15 | func Error(msg string, args ...interface{}) { log.Error(getFullLogPrefix()+msg, args...) }
16 | func Crit(msg string, args ...interface{}) { log.Crit(getFullLogPrefix()+msg, args...) }
17 |
18 | // Prettier error logging.
19 | func Errorf(msg string, err error, args ...interface{}) {
20 | wrappedErr := fmt.Errorf(getFullLogPrefix()+msg, err)
21 | log.Error(wrappedErr.Error(), args...)
22 | }
23 |
--------------------------------------------------------------------------------
/contracts/src/vendor/ERC1967Proxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Modifications Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
22 |
23 | // This file is for exporting the ERC1967Proxy contract ABI only
24 |
--------------------------------------------------------------------------------
/services/sidecar/utils/errors.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | func AsAny(err error, targets ...any) bool {
8 | for _, target := range targets {
9 | if errors.As(err, target) {
10 | return true
11 | }
12 | }
13 | return false
14 | }
15 |
16 | type CategorizedError[T comparable] struct {
17 | Cat T
18 | Err error
19 | }
20 |
21 | func NewCategorizedError[T comparable](cat T, err error) *CategorizedError[T] {
22 | return &CategorizedError[T]{Cat: cat, Err: err}
23 | }
24 |
25 | func (e *CategorizedError[T]) Unwrap() error { return e.Err }
26 | func (e *CategorizedError[T]) Category() T { return e.Cat }
27 | func (e *CategorizedError[T]) Error() string { return e.Err.Error() }
28 |
29 | // Shallow comparison: categories must be equal.
30 | func (e *CategorizedError[T]) Is(target error) bool {
31 | err, ok := target.(*CategorizedError[T])
32 | if !ok {
33 | return false
34 | }
35 | return e.Cat == err.Cat
36 | }
37 |
--------------------------------------------------------------------------------
/services/sidecar/utils/fmt/fmt.go:
--------------------------------------------------------------------------------
1 | package fmt
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | )
7 |
8 | func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
9 | return fmt.Fprintf(w, format, a...)
10 | }
11 |
12 | func Printf(format string, a ...any) (n int, err error) {
13 | return fmt.Printf(format, a...)
14 | }
15 |
16 | func Sprintf(format string, a ...any) string {
17 | return fmt.Sprintf(format, a...)
18 | }
19 |
20 | func Fprint(w io.Writer, a ...any) (n int, err error) {
21 | return fmt.Fprint(w, a...)
22 | }
23 |
24 | func Print(a ...any) (n int, err error) {
25 | return fmt.Print(a...)
26 | }
27 |
28 | func Sprint(a ...any) string {
29 | return fmt.Sprint(a...)
30 | }
31 |
32 | func Fprintln(w io.Writer, a ...any) (n int, err error) {
33 | return fmt.Fprintln(w, a...)
34 | }
35 |
36 | func Println(a ...any) (n int, err error) {
37 | return fmt.Println(a...)
38 | }
39 |
40 | func Sprintln(a ...any) string {
41 | return fmt.Sprintln(a...)
42 | }
43 |
--------------------------------------------------------------------------------
/config/example/.sp_geth.env:
--------------------------------------------------------------------------------
1 | # This dotenv contains the env variables for running sp-geth via `sbin/start_sp_geth.sh`.
2 | # Each variable corresponds to a flag defined for the `sp-geth` binary.
3 | # Note, however that only a subset of these flags are configurable, as allowed by the script.
4 | # Run `geth --help` for more details.
5 |
6 | # Specular network ID
7 | NETWORK_ID=13527
8 | # Address to use for http/ws/auth.
9 | ADDRESS=0.0.0.0
10 | # Ports. Note: if using docker, ports must be appropriately exposed.
11 | HTTP_PORT=4011
12 | WS_PORT=4012
13 | AUTH_PORT=4013
14 | # Path to genesis JSON.
15 | GENESIS_PATH=./genesis.json
16 | # Path to data directory, where node data will be stored.
17 | # Does not need to already exist.
18 | DATA_DIR=./sp-geth-data/
19 | # Secret shared between sp-geth and sp-magi. You can generate this file, e.g. with
20 | # `openssl rand -hex 32 >> jwt_secret.txt`
21 | JWT_SECRET_PATH=./jwt_secret.txt
22 | # Defining this variable will start SPGETH in Archive Mode
23 | ARCHIVE_NODE=1
24 |
--------------------------------------------------------------------------------
/config/e2e_test/genesis_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "l2ChainID": 13527,
3 | "l2GenesisBlockNonce": "0x0",
4 | "l2GenesisBlockGasLimit": "0x0",
5 | "l2GenesisBlockDifficulty": "0x0",
6 | "l2GenesisBlockNumber": "0x0",
7 | "l2GenesisBlockGasUsed": "0x0",
8 | "l2GenesisBlockBaseFeePerGas": "0x1",
9 | "l2GenesisBlockExtraData": "0x",
10 | "l2PredeployOwner": "0x000000000000000000000000000000000000dEaD",
11 | "l2FeesWithdrawalAddress": "0x000000000000000000000000000000000000dEaD",
12 | "l2FeesMinWithdrwalAmount": "0x0",
13 | "l1FeeOverhead": "0x1",
14 | "l1FeeScalar": "0x1",
15 | "alloc": {
16 | "2a00000000000000000000000000000000000011": {
17 | "balance": "1000000000000000000000000000"
18 | },
19 | "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": {
20 | "balance": "10000000000000000000"
21 | },
22 | "0x90F79bf6EB2c4f870365E785982E1f101E93b906": {
23 | "balance": "10000000000000000000"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/config/local_docker/genesis_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "l2ChainID": 13527,
3 | "l2GenesisBlockNonce": "0x0",
4 | "l2GenesisBlockGasLimit": "0x0",
5 | "l2GenesisBlockDifficulty": "0x0",
6 | "l2GenesisBlockNumber": "0x0",
7 | "l2GenesisBlockGasUsed": "0x0",
8 | "l2GenesisBlockBaseFeePerGas": "0x1",
9 | "l2GenesisBlockExtraData": "0x",
10 | "l2PredeployOwner": "0x000000000000000000000000000000000000dEaD",
11 | "l2FeesWithdrawalAddress": "0x000000000000000000000000000000000000dEaD",
12 | "l2FeesMinWithdrwalAmount": "0x0",
13 | "l1FeeOverhead": "0x1",
14 | "l1FeeScalar": "0x1",
15 | "alloc": {
16 | "2a00000000000000000000000000000000000011": {
17 | "balance": "1000000000000000000000000000"
18 | },
19 | "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": {
20 | "balance": "10000000000000000000"
21 | },
22 | "0x90F79bf6EB2c4f870365E785982E1f101E93b906": {
23 | "balance": "10000000000000000000"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/services/sidecar/utils/map.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "sync"
4 |
5 | // sync.Map with type enforcement
6 | type Map[K comparable, V any] struct{ m sync.Map }
7 |
8 | func (m *Map[K, V]) Load(key K) (value V) {
9 | v, ok := m.m.Load(key)
10 | if !ok {
11 | return value
12 | }
13 | return v.(V)
14 | }
15 |
16 | func (m *Map[K, V]) Store(key K, value V) { m.m.Store(key, value) }
17 |
18 | func (m *Map[K, V]) LoadOrStore(key K, value V) V {
19 | a, _ := m.m.LoadOrStore(key, value)
20 | return a.(V)
21 | }
22 |
23 | func (m *Map[K, V]) LoadAndStore(key K, value V) V {
24 | v := m.Load(key)
25 | m.Store(key, value)
26 | return v
27 | }
28 |
29 | func (m *Map[K, V]) LoadAndDelete(key K) (value V) {
30 | v, loaded := m.m.LoadAndDelete(key)
31 | if !loaded {
32 | return value
33 | }
34 | return v.(V)
35 | }
36 |
37 | func (m *Map[K, V]) Delete(key K) { m.m.Delete(key) }
38 |
39 | func (m *Map[K, V]) Range(f func(key K, value V) bool) {
40 | m.m.Range(func(key, value any) bool { return f(key.(K), value.(V)) })
41 | }
42 |
--------------------------------------------------------------------------------
/config/kubernetes/genesis_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "l2ChainID": 13527,
3 | "l2GenesisBlockNonce": "0x0",
4 | "l2GenesisBlockGasLimit": "0x0",
5 | "l2GenesisBlockDifficulty": "0x0",
6 | "l2GenesisBlockNumber": "0x0",
7 | "l2GenesisBlockGasUsed": "0x0",
8 | "l2GenesisBlockBaseFeePerGas": "0x1",
9 | "l2GenesisBlockExtraData": "0x",
10 | "l2PredeployOwner": "0x000000000000000000000000000000000000dEaD",
11 | "l2FeesWithdrawalAddress": "0x000000000000000000000000000000000000dEaD",
12 | "l2FeesMinWithdrwalAmount": "0x0",
13 | "l1FeeOverhead": "0x1",
14 | "l1FeeScalar": "0x1",
15 | "alloc": {
16 | "2a00000000000000000000000000000000000011": {
17 | "balance": "1000000000000000000000000000"
18 | },
19 | "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": {
20 | "balance": "1000000000000000000000000000"
21 | },
22 | "0x90F79bf6EB2c4f870365E785982E1f101E93b906": {
23 | "balance": "1000000000000000000000000000"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/contracts/src/bridge/mintable/IMintableERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | // The following code is a derivative work of the code from the Optimism contributors
4 | // https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/universal/IOptimismMintableERC20.sol
5 | // commit hash: c93958755b4f6ab7f95cc0b2459f39ca95c06684
6 |
7 | pragma solidity ^0.8.4;
8 |
9 | import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
10 |
11 | /// @title IMintableERC20
12 | /// @notice This interface is available on the MintableERC20 contract.
13 | /// We declare it as a separate interface so that it can be used in
14 | /// custom implementations of MintableERC20.
15 | interface IMintableERC20 is IERC165 {
16 | function REMOTE_TOKEN() external view returns (address);
17 |
18 | function BRIDGE() external returns (address);
19 |
20 | function mint(address _to, uint256 _amount) external;
21 |
22 | function burn(address _from, uint256 _amount) external;
23 | }
24 |
--------------------------------------------------------------------------------
/sbin/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 | cd /specular/workspace
4 |
5 | # remove all locks since this need to run first
6 | # LOCKFILES=`ls .*.lock 2> /dev/null | wc -l`
7 | # if [[ $LOCKFILES -gt 0 ]]; then
8 | # echo "Removing lockfiles"
9 | # rm .*.lock
10 | # fi
11 |
12 | echo "Setting environment variables"
13 | export INFURA_KEY=$(cat infura_pk.txt)
14 | export DEPLOYER_PRIVATE_KEY=$(cat deployer_pk.txt)
15 | export SEQUENCER_PRIVATE_KEY=$(cat sequencer_pk.txt)
16 | export VALIDATOR_PRIV_KEY=$(cat validator_pk.txt)
17 | set -o allexport
18 | . .sp_geth.env
19 | . .sp_magi.env
20 | . .contracts.env
21 | . .genesis.env
22 | . .paths.env
23 | . .sidecar.env
24 | set +o allexport
25 |
26 | case "$1" in
27 | deploy)
28 | # Run the main container command.
29 | echo "Running deploy for genesis and JWT"
30 | /specular/sbin/generate_jwt_secret.sh
31 | /specular/sbin/deploy_l1_contracts.sh -y
32 | ;;
33 | start)
34 | shift
35 | /specular/sbin/$@
36 | ;;
37 | *)
38 | echo "Unknown Command"
39 | exit 1
40 | ;;
41 | esac
42 |
--------------------------------------------------------------------------------
/sbin/clean_deployment.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | SBIN=$(dirname "$(readlink -f "$0")")
3 | ROOT_DIR=$SBIN/..
4 |
5 | PATHS_ENV=".paths.env"
6 | if ! test -f "$PATHS_ENV"; then
7 | echo "Expected dotenv at $PATHS_ENV (does not exist)."
8 | exit
9 | fi
10 | echo "Using dotenv: $PATHS_ENV"
11 | . $PATHS_ENV
12 |
13 | GENESIS_ENV=".genesis.env"
14 | if test -f "$GENESIS_ENV"; then
15 | . $GENESIS_ENV
16 | fi
17 |
18 | if test -f "$GENESIS_PATH"; then
19 | echo "Removing $GENESIS_PATH"
20 | rm $GENESIS_PATH
21 | fi
22 | if test -f "$GENESIS_EXPORTED_HASH_PATH"; then
23 | echo "Removing $GENESIS_EXPORTED_HASH_PATH"
24 | rm $GENESIS_EXPORTED_HASH_PATH
25 | fi
26 | if test -f "$ROLLUP_CFG_PATH"; then
27 | echo "Removing $ROLLUP_CFG_PATH"
28 | rm $ROLLUP_CFG_PATH
29 | fi
30 | DEPLOYMENTS_ENV=".deployments.env"
31 | if test -f "$DEPLOYMENTS_ENV"; then
32 | echo "Removing $DEPLOYMENTS_ENV"
33 | rm $DEPLOYMENTS_ENV
34 | fi
35 |
36 | echo "Removing deployment files in $CONTRACTS_DIR/deployments/$L1_NETWORK"
37 | rm -rf $CONTRACTS_DIR/deployments/$L1_NETWORK
38 | rm -f .deployed
39 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Forge Tests
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | pre_job:
11 | runs-on: ubuntu-latest
12 | outputs:
13 | should_skip: ${{ steps.skip_check.outputs.should_skip }}
14 | steps:
15 | - id: skip_check
16 | uses: fkirc/skip-duplicate-actions@v5
17 | with:
18 | concurrent_skipping: same_content_newer
19 |
20 | test:
21 | needs: pre_job
22 | if: needs.pre_job.outputs.should_skip != 'true'
23 | runs-on: ubuntu-latest
24 | defaults:
25 | run:
26 | working-directory: contracts
27 |
28 | steps:
29 | - uses: actions/checkout@v3
30 | - uses: ./.github/actions/node
31 | - uses: ./.github/actions/foundry
32 |
33 | - name: Run forge install
34 | run: forge install
35 |
36 | - name: Run tests
37 | env:
38 | RPC_URL: ${{ secrets.RPC_URL }}
39 | run: forge test -vvv
40 |
41 | - name: Check forge tests names
42 | run: pnpm lint:forge-tests:check
43 |
--------------------------------------------------------------------------------
/services/sidecar/internal/service/config/cli_config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/urfave/cli/v2"
7 |
8 | "github.com/specularL2/specular/services/sidecar/rollup/services"
9 | )
10 |
11 | type CLIExtractor struct {
12 | systemConfig *services.SystemConfig
13 | }
14 |
15 | func (t *CLIExtractor) ExtractFromCLIContext(cliCtx *cli.Context) error {
16 | var err error
17 | t.systemConfig, err = services.ParseSystemConfig(cliCtx)
18 | if err != nil {
19 | return err
20 | }
21 | return nil
22 | }
23 |
24 | // Nasty trick to extract parsed SystemConfig from the urfave/cli package wrapper and serve properly from a provider
25 | func NewSystemConfig(cfg *Config) (*services.SystemConfig, error) {
26 | cliExtractor := &CLIExtractor{}
27 |
28 | cliApp := &cli.App{
29 | Name: cfg.ServiceName,
30 | Usage: cfg.UsageDesc,
31 | Action: cliExtractor.ExtractFromCLIContext,
32 | }
33 | cliApp.Flags = services.CLIFlags()
34 |
35 | if err := cliApp.Run(os.Args); err != nil {
36 | return nil, err
37 | }
38 |
39 | return cliExtractor.systemConfig, nil
40 | }
41 |
--------------------------------------------------------------------------------
/packages/onboarding/src/index.ts:
--------------------------------------------------------------------------------
1 | import "dotenv/config";
2 | import { BigNumber, ethers } from "ethers";
3 | import fastify from "fastify";
4 |
5 | import {
6 | OnboardingServiceConfig,
7 | OnboardingService,
8 | relayerPlugin,
9 | } from "./onboarding";
10 |
11 | export default async function main() {
12 | const config: OnboardingServiceConfig = {
13 | l1ProviderEndpoint: process.env.L1_PROVIDER_ENDPOINT!,
14 | l2ProviderEndpoint: process.env.L2_PROVIDER_ENDPOINT!,
15 | l2FunderPrivateKey: process.env.L2_FUNDER_PRIVATE_KEY!,
16 | l1PortalAddress: process.env.L1_PORTAL_ADDRESS!,
17 | depositFundingThreshold: process.env.DEPOSIT_FUNDING_THRESHOLD
18 | ? BigNumber.from(process.env.DEPOSIT_FUNDING_THRESHOLD)
19 | : ethers.utils.parseEther("0.01"),
20 | };
21 |
22 | const relayer = new OnboardingService(config);
23 | await relayer.start();
24 |
25 | const app = fastify({
26 | logger: true,
27 | trustProxy: true,
28 | });
29 | app.register(relayerPlugin, { onboarding: relayer });
30 |
31 | await app.listen({ port: Number(process.env.APP_PORT ?? 3092) });
32 | }
33 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build and release
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | packages: write
14 | steps:
15 | - name: Cache git folder
16 | uses: actions/cache@v3
17 | with:
18 | path: ./.git
19 | key: git-folder
20 |
21 | - uses: actions/checkout@v3
22 | with:
23 | submodules: 'recursive'
24 |
25 | - uses: ./.github/actions/node
26 |
27 | - uses: ./.github/actions/foundry
28 |
29 | - uses: ./.github/actions/go
30 |
31 | - name: Make specular
32 | run: make
33 |
34 | - name: Bump version and push tag
35 | id: tag_version
36 | uses: mathieudutour/github-tag-action@v6.1
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 |
40 | - uses: ncipollo/release-action@v1
41 | with:
42 | tag: ${{ steps.tag_version.outputs.new_tag }}
43 | prerelease: true
44 | artifacts: "artifacts/*"
45 |
--------------------------------------------------------------------------------
/docs/deployingERC20.md:
--------------------------------------------------------------------------------
1 | # Deploying ERC20 Tokens
2 |
3 | This is a quick guide on how to deploy L2 versions of ERC20 tokens already existing on L1.
4 |
5 | ## Deploying the L2 contract
6 |
7 | The L2 token must implement the [`IMintableERC20`](../contracts/src/bridge/mintable/IMintableERC20.sol) interface.
8 |
9 | The easiest way deploying the L2 token contract is by calling the `MintableERC20Factory` contract.
10 | This is a predeploy at `0x2A000000000000000000000000000000000000F0`
11 |
12 | See [this script](../contracts/scripts/bridge/deployERC20Token.ts) `MintableERC20Factory` for how to interact with the contract.
13 | It can also be used as a CLI tool as follows:
14 |
15 | ```
16 | npx ts-node ./scripts/bridge/deployERC20Token.ts --rpc http://127.0.0.1:4011 --name test --symbol TT --address 0x000000000000000000000000000000000000dead
17 | ```
18 |
19 | ## Adding the contract pair to the token list
20 |
21 | Tokens can be added to the [Specular Token List](https://github.com/SpecularL2/specular-token-list) to be accessible through the Specular Bridge UI.
22 |
23 | This is also how we keep track of canonical token mappings.
24 |
--------------------------------------------------------------------------------
/packages/onboarding/src/onboarding/types.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers";
2 | import { Static, Type } from "@sinclair/typebox";
3 |
4 | export type CrossDomainMessage = {
5 | version: BigNumber;
6 | nonce: BigNumber;
7 | sender: string;
8 | target: string;
9 | value: BigNumber;
10 | gasLimit: BigNumber;
11 | data: string;
12 | };
13 |
14 | export const FundDepositRequestBody = Type.Object({
15 | version: Type.String(),
16 | nonce: Type.String(),
17 | sender: Type.String(),
18 | target: Type.String(),
19 | value: Type.String(),
20 | gasLimit: Type.String(),
21 | data: Type.String(),
22 | depositHash: Type.String(),
23 | });
24 |
25 | export type FundDepositRequestBodyType = Static;
26 |
27 | export const FundDepositReplyBody = Type.Object({
28 | txHash: Type.String(),
29 | });
30 |
31 | export type FundDepositReplyBodyType = Static;
32 |
33 | export const FundDepositReplyErrorBody = Type.Object({
34 | error: Type.String(),
35 | });
36 |
37 | export type FundDepositReplyErrorBodyType = Static<
38 | typeof FundDepositReplyErrorBody
39 | >;
40 |
--------------------------------------------------------------------------------
/config/example/.sp_magi.env:
--------------------------------------------------------------------------------
1 | # This dotenv contains the env variables for running sp-magi via `sbin/start_sp_magi.sh`.
2 | # Each variable corresponds to a flag defined for the `sp-magi` binary.
3 | # Run `magi --help` for more details.
4 |
5 | # Network can either be an existing network or a rollup.json path (relative to cwd).
6 | # Note: if specifying a JSON files, make sure it's prefixed with `sp_` to ensure it's
7 | # parsed according to specular's protocol.
8 | NETWORK=sp_rollup.json
9 | # Secret shared between sp-geth and sp-magi. You can generate this file, e.g. with
10 | # `openssl rand -hex 32 >> jwt_secret.txt`
11 | JWT_SECRET_PATH=./jwt_secret.txt
12 | # L1 and L2 URLs, ports. Note: if using docker, ports must be appropriately exposed.
13 | L1_RPC_URL=http://127.0.0.1:8545
14 | L2_RPC_URL=http://127.0.0.1:4011
15 | L2_ENGINE_URL=http://127.0.0.1:4013
16 | RPC_PORT=10500
17 | # Sync flags
18 | SYNC_MODE=full
19 | # Devnet flag
20 | DEVNET=true
21 | # Local sequencer flags. If SEQUENCER is true, SEQUENCER_MAX_SAFE_LAG is required.
22 | SEQUENCER=true
23 | SEQUENCER_MAX_SAFE_LAG=200
24 | SEQUENCER_PK_FILE=./sequencer_pk.txt
25 | WATCHER_DELAY=4000
26 |
--------------------------------------------------------------------------------
/config/example/genesis_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "l2ChainID": 13527,
3 | "l2GenesisBlockNonce": "0x0",
4 | "l2GenesisBlockGasLimit": "0x0",
5 | "l2GenesisBlockDifficulty": "0x0",
6 | "l2GenesisBlockNumber": "0x0",
7 | "l2GenesisBlockGasUsed": "0x0",
8 | "l2GenesisBlockBaseFeePerGas": "0x0",
9 | "l2GenesisBlockExtraData": "0x",
10 | "l2PredeployOwner": "0x000000000000000000000000000000000000dEaD",
11 | "l2FeesWithdrawalAddress": "0x000000000000000000000000000000000000dEaD",
12 | "l2FeesMinWithdrwalAmount": "0x0",
13 | "l1FeeOverhead": "0x0",
14 | "l1FeeScalar": "0x0",
15 | "alloc": {
16 | "2a00000000000000000000000000000000000011": {
17 | "balance": "1000000000000000000000000000"
18 | },
19 | "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {
20 | "balance": "10000000000000000000"
21 | },
22 | "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": {
23 | "balance": "10000000000000000000"
24 | },
25 | "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": {
26 | "balance": "10000000000000000000"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/contracts/src/libraries/SafeCall.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.4;
3 |
4 | /**
5 | * @title SafeCall
6 | * @notice Perform low level safe calls
7 | */
8 | library SafeCall {
9 | /**
10 | * @notice Perform a low level call without copying any returndata
11 | *
12 | * @param _target Address to call
13 | * @param _gas Amount of gas to pass to the call
14 | * @param _value Amount of value to pass to the call
15 | * @param _calldata Calldata to pass to the call
16 | */
17 | function call(address _target, uint256 _gas, uint256 _value, bytes memory _calldata) internal returns (bool) {
18 | bool _success;
19 | assembly {
20 | _success :=
21 | call(
22 | _gas, // gas
23 | _target, // recipient
24 | _value, // ether value
25 | add(_calldata, 0x20), // inloc
26 | mload(_calldata), // inlen
27 | 0, // outloc
28 | 0 // outlen
29 | )
30 | }
31 | return _success;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docker/build-master.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:bullseye-slim as base
2 |
3 | # Versions
4 | ENV GOVERSION=1.21.5
5 | ENV NODE_MAJOR=16
6 |
7 | RUN apt update && apt install -y ca-certificates curl gnupg
8 | RUN mkdir -p /etc/apt/keyrings
9 | RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
10 |
11 | # Node Version
12 | RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
13 |
14 | RUN apt-get update && apt-get install nodejs -y
15 |
16 | RUN apt install -y libudev-dev build-essential ca-certificates clang libpq-dev libssl-dev pkg-config lsof lld bash python3 make g++ musl-dev git
17 |
18 | RUN apt install -y linux-headers-generic wget
19 | RUN npm install -g pnpm
20 | RUN wget https://go.dev/dl/go$GOVERSION.linux-amd64.tar.gz && tar -C /usr/local -xzf go$GOVERSION.linux-amd64.tar.gz && rm go$GOVERSION.linux-amd64.tar.gz
21 |
22 |
23 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
24 | ENV PATH "$PATH:/root/.cargo/bin"
25 | ENV PATH="$PATH:/usr/local/go/bin"
26 |
--------------------------------------------------------------------------------
/packages/onboarding/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@specularl2/onboarding-services",
3 | "version": "0.0.1",
4 | "type": "commonjs",
5 | "main": "dist/index.js",
6 | "license": "Apache-2.0",
7 | "scripts": {
8 | "start": "node dist/index.js",
9 | "dev": "ts-node index.ts",
10 | "prettier": "prettier --write .",
11 | "lint": "tsc --noEmit && eslint . --fix",
12 | "build": "tsc --build",
13 | "clean": "tsc --build --clean"
14 | },
15 | "dependencies": {
16 | "@ethersproject/experimental": "^5.7.0",
17 | "@fastify/cors": "^8.2.1",
18 | "@fastify/type-provider-typebox": "^3.0.0",
19 | "@sinclair/typebox": "^0.26.8",
20 | "@specularl2/sdk": "workspace:*",
21 | "dotenv": "^16.0.3",
22 | "ethers": "^5.7.2",
23 | "fastify": "^4.15.0"
24 | },
25 | "devDependencies": {
26 | "@ethersproject/providers": "^5.7.2",
27 | "@types/node": "^18.14.2",
28 | "@typescript-eslint/eslint-plugin": "^5.62.0",
29 | "@typescript-eslint/parser": "^5.62.0",
30 | "eslint": "^8.56.0",
31 | "prettier": "^2.8.8",
32 | "ts-node": "^10.9.2",
33 | "typescript": "^5.1.6",
34 | "typescript-collections": "^1.3.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/charts/specular/templates/tx-fuzz-pod.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.enabled.txFuzz -}}
2 | apiVersion: v1
3 | kind: Pod
4 | metadata:
5 | creationTimestamp: null
6 | labels:
7 | io.specular.network/sp-network: "true"
8 | io.specular.service: tx-fuzz
9 | name: tx-fuzz
10 | spec:
11 | initContainers:
12 | - name: wait-for-sp-magi
13 | image: busybox:latest
14 | command: ["sh", "-c", "until [ -f /tmp/.start_sp_magi.sh.lock ]; do sleep 2; done"]
15 | volumeMounts:
16 | - mountPath: /tmp
17 | name: {{ .Values.volume.efs.name }}
18 | containers:
19 | - name: tx-fuzz
20 | command: ["sh", "-c", "/entrypoint.sh"]
21 | image: "{{ .Values.tx_fuzz_image.registry }}/{{ .Values.tx_fuzz_image.name }}:{{ .Values.tx_fuzz_image.tag }}"
22 | resources:
23 | {{- .Values.default_resources | toYaml | nindent 10 }}
24 | tty: true
25 | volumeMounts:
26 | - mountPath: /tmp
27 | name: {{ .Values.volume.efs.name }}
28 | workingDir: /tmp
29 | restartPolicy: Always
30 | volumes:
31 | - name: {{ .Values.volume.efs.name }}
32 | persistentVolumeClaim:
33 | claimName: {{ .Values.volume.efs.name }}
34 | status: {}
35 | {{- end -}}
36 |
--------------------------------------------------------------------------------
/config/example/.sidecar.env:
--------------------------------------------------------------------------------
1 | # This dotenv contains the env variables for configuring the
2 | # execution of the sidecar service.
3 |
4 | # Path to protocol configuration (rollup.json)
5 | ROLLUP_CFG_PATH=sp_rollup.json
6 | # L1 RPC URL
7 | L1_ENDPOINT=http://172.17.0.1:8545
8 | # L2 RPC URL
9 | L2_ENDPOINT=http://127.0.0.1:4011
10 | # Clef endpoint for disseminator/validator remote signing.
11 | CLEF_ENDPOINT=
12 |
13 | # disseminator configuration
14 | # The following parameters are only required if DISSEMINATOR=true
15 | DISSEMINATOR=true
16 | # Path to file containing disseminator private key (in plain).
17 | # Note: this can be the same as the sequencer (as in the example here),
18 | # or a different key altogether, depending on how the protocol has been configured.
19 | DISSEMINATOR_PK_PATH=sequencer_pk.txt
20 | DISSEMINATOR_INTERVAL=2
21 | DISSEMINATOR_SUB_SAFETY_MARGIN=8
22 | DISSEMINATOR_TARGET_BATCH_SIZE=4096
23 | DISSEMINATOR_MAX_BATCH_SIZE=120000
24 | DISSEMINATOR_MAX_SAFE_LAG=200
25 | DISSEMINATOR_MAX_SAFE_LAG_DELTA=40
26 | # validator configuration
27 | # The following parameters are only required if VALIDATOR=true
28 | VALIDATOR=true
29 | # Path to file containing validator private key (in plain).
30 | VALIDATOR_PK_PATH=validator_pk.txt
31 |
--------------------------------------------------------------------------------
/contracts/src/IDAProvider.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | /**
22 | * @notice Data availability interface to rollup contracts.
23 | */
24 | interface IDAProvider {
25 | /**
26 | * Verifies proof of inclusion of a transaction by the data availability provider.
27 | * If verification fails, the function reverts.
28 | * @param encodedTx RLP-encoded transaction.
29 | * @param proof DA-specific membership proof.
30 | */
31 | function verifyTxInclusion(bytes memory encodedTx, bytes calldata proof) external view;
32 | }
33 |
--------------------------------------------------------------------------------
/contracts/src/libraries/Predeploys.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | library Predeploys {
22 | // @notice Address of the L1Oracle predeploy.
23 | address internal constant L1_ORACLE = 0x2a00000000000000000000000000000000000010;
24 |
25 | // @notice Address of the L2Portal predeploy.
26 | address internal constant L2_PORTAL = 0x2A00000000000000000000000000000000000011;
27 |
28 | // @notice Address of the L2StandardBridge predeploy.
29 | address internal constant L2_STANDARD_BRIDGE = 0x2a00000000000000000000000000000000000012;
30 | }
31 |
--------------------------------------------------------------------------------
/contracts/src/challenge/verifier/VerificationContextLib.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | library VerificationContextLib {
22 | struct RawContext {
23 | bytes encodedTx;
24 | // Transaction context.
25 | address l2BlockCoinbase;
26 | uint256 l2BlockNumber;
27 | uint256 l2BlockTimestamp;
28 | }
29 |
30 | function txContextHash(RawContext calldata ctx) internal pure returns (bytes32) {
31 | return keccak256(abi.encodePacked(ctx.l2BlockCoinbase, ctx.l2BlockNumber, ctx.l2BlockTimestamp));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/contracts/deploy/l1/02_deploy_Rollup.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { HardhatRuntimeEnvironment } from "hardhat/types";
3 | import { DeployFunction } from "hardhat-deploy/types";
4 |
5 | import { deployUUPSProxiedContract, getProxyName } from "../utils";
6 |
7 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
8 | const { deployments, getNamedAccounts } = hre;
9 | const { deployer } = await getNamedAccounts();
10 | const sequencerInboxProxyAddress = (
11 | await deployments.get(getProxyName("SequencerInbox"))
12 | ).address;
13 | const verifierProxyAddress = (await deployments.get(getProxyName("Verifier")))
14 | .address;
15 |
16 | const config = {
17 | vault: process.env.DEPLOYER_ADDRESS,
18 | daProvider: sequencerInboxProxyAddress,
19 | verifier: verifierProxyAddress,
20 | confirmationPeriod: 12, // TODO: move to config
21 | challengePeriod: 0,
22 | minimumAssertionPeriod: 0,
23 | baseStakeAmount: 0,
24 | validators: [process.env.VALIDATOR_ADDRESS]
25 | };
26 |
27 | const args = [config];
28 |
29 | console.log("Deploying Rollup with args:", { args })
30 | await deployUUPSProxiedContract(hre, deployer, "Rollup", args);
31 | };
32 |
33 | export default func;
34 | func.tags = ["Rollup", "L1", "Stage0"];
35 |
--------------------------------------------------------------------------------
/sbin/utils/utils.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Converts a path to be relative to another directory.
4 | relpath() {
5 | echo $(python3 -c "import os.path; print(os.path.relpath('$1', '$2'))")
6 | }
7 |
8 | # Requests a user to confirm the given prompt ($1).
9 | guard() {
10 | read -r -p "$1 " response
11 | if ! [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
12 | exit 1
13 | fi
14 | }
15 |
16 | # Requests a user to confirm that overwriting
17 | # file ($1) is okay, if it exists.
18 | # doesn't ask if -y is set
19 | guard_overwrite() {
20 |
21 | if test -f $1; then
22 |
23 | if [[ "$2" = "false" ]]; then
24 | read -r -p "Overwrite $1 with a new file? [y/N] " response
25 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
26 | echo "Removing $1"
27 | rm $1
28 | else
29 | exit 1
30 | fi
31 | fi
32 | fi
33 | }
34 |
35 | # Requires that a dotenv named $1 exists at a path ($2).
36 | reqdotenv() {
37 | if ! test -f "$2"; then
38 | echo "Expected $1 dotenv at $2 (not found)."
39 | exit 1
40 | fi
41 | echo "Using $1 dotenv: $2"
42 | . $2
43 | }
44 |
45 | # Requires that all env variables named in $@ are set.
46 | reqenv() {
47 | for var in "$@"; do
48 | if [ -z ${!var+x} ]; then
49 | echo "$var is required but not set"
50 | exit 1
51 | fi
52 | done
53 | }
54 |
--------------------------------------------------------------------------------
/contracts/scripts/upgrade_placeholder.ts:
--------------------------------------------------------------------------------
1 | // run with: npx hardhat run --network local
2 | import dotenv from "dotenv";
3 | import { keccak256 } from "ethereumjs-util";
4 | import { ethers, upgrades } from "hardhat";
5 |
6 | dotenv.config({ path: __dirname + "/../.env" });
7 |
8 | const main = async () => {
9 | const UUPSPlaceholderFactory =
10 | await ethers.getContractFactory("UUPSPlaceholder");
11 | const FaucetFactory = await ethers.getContractFactory("Faucet");
12 |
13 | const proxyAddress = "0xff00000000000000000000000000000000000001";
14 |
15 | console.log(
16 | "Implementation address: " +
17 | (await upgrades.erc1967.getImplementationAddress(proxyAddress)),
18 | );
19 | console.log(
20 | "Admin address: " + (await upgrades.erc1967.getAdminAddress(proxyAddress)),
21 | );
22 |
23 | const proxy = await upgrades.forceImport(
24 | proxyAddress,
25 | UUPSPlaceholderFactory,
26 | );
27 |
28 | const init = await proxy.initialize();
29 | console.log({ init });
30 |
31 | const upgraded = await upgrades.upgradeProxy(proxy.address, FaucetFactory);
32 | console.log({ upgraded });
33 |
34 | const tx = await upgraded.owner();
35 | console.log({ tx });
36 | };
37 |
38 | main()
39 | .then(() => process.exit(0))
40 | .catch((error) => {
41 | console.error(error);
42 | process.exit(1);
43 | });
44 |
--------------------------------------------------------------------------------
/services/sidecar/rollup/services/disseminator/interface.go:
--------------------------------------------------------------------------------
1 | package disseminator
2 |
3 | import (
4 | "context"
5 | "math/big"
6 | "time"
7 |
8 | "github.com/ethereum/go-ethereum/beacon/engine"
9 | ethTypes "github.com/ethereum/go-ethereum/core/types"
10 |
11 | "github.com/specularL2/specular/services/sidecar/rollup/rpc/eth"
12 | "github.com/specularL2/specular/services/sidecar/rollup/types"
13 | )
14 |
15 | type Config interface{ GetDisseminationInterval() time.Duration }
16 |
17 | type ForkChoiceState = engine.ForkchoiceStateV1
18 | type ForkChoiceResponse = engine.ForkChoiceResponse
19 | type BuildPayloadResponse = engine.ForkChoiceResponse
20 |
21 | type BatchBuilder interface {
22 | Enqueue(block *ethTypes.Block) error
23 | LastEnqueued() types.BlockID
24 | Build(l1Head types.BlockID, currentLag uint64) ([]byte, error)
25 | Advance()
26 | Reset(lastEnqueued types.BlockID)
27 | }
28 |
29 | type TxManager interface {
30 | AppendTxBatch(ctx context.Context, batch []byte) (*ethTypes.Receipt, error)
31 | }
32 |
33 | type L2Client interface {
34 | EnsureDialed(ctx context.Context) error
35 | BlockNumber(ctx context.Context) (uint64, error)
36 | BlockByNumber(ctx context.Context, number *big.Int) (*ethTypes.Block, error)
37 | HeaderByTag(ctx context.Context, tag eth.BlockTag) (*ethTypes.Header, error)
38 | }
39 |
40 | type ErrGroup interface{ Go(f func() error) }
41 |
--------------------------------------------------------------------------------
/.github/stale-workflows/slither.yml:
--------------------------------------------------------------------------------
1 | name: Slither Analysis
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | pre_job:
11 | runs-on: ubuntu-latest
12 | outputs:
13 | should_skip: ${{ steps.skip_check.outputs.should_skip }}
14 | steps:
15 | - id: skip_check
16 | uses: fkirc/skip-duplicate-actions@v5
17 | with:
18 | concurrent_skipping: same_content_newer
19 | analyze:
20 | needs: pre_job
21 | if: needs.pre_job.outputs.should_skip != 'true'
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v3
25 | - uses: ./.github/actions/node
26 |
27 | - name: Generate lock file
28 | working-directory: ./contracts
29 | run: pnpm make-dedicated-lockfile
30 |
31 | - name: Run Slither
32 | uses: crytic/slither-action@v0.3.0
33 | id: slither
34 | with:
35 | sarif: results.sarif
36 | node-version: 16
37 | target: ./contracts
38 | fail-on: none
39 | slither-args: '--filter-paths "(lib/|@openzeppelin|hardhat|src/libraries/BytesLib.sol|test/)"'
40 |
41 | - name: Upload SARIF file
42 | uses: github/codeql-action/upload-sarif@v2
43 | if: github.repository == 'SpecularL2/specular'
44 | with:
45 | sarif_file: ${{ steps.slither.outputs.sarif }}
46 |
--------------------------------------------------------------------------------
/services/sidecar/rollup/types/block_id.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/ethereum/go-ethereum/common"
5 | "github.com/ethereum/go-ethereum/core/types"
6 |
7 | "github.com/specularL2/specular/services/sidecar/utils/fmt"
8 | )
9 |
10 | var (
11 | EmptyBlockID = BlockID{}
12 | EmptyBlockRef = BlockRef{}
13 | )
14 |
15 | type BlockID struct {
16 | Number uint64 `json:"number"`
17 | Hash common.Hash `json:"hash"`
18 | }
19 |
20 | func NewBlockID(number uint64, hash common.Hash) BlockID { return BlockID{number, hash} }
21 |
22 | func NewBlockIDFromHeader(header *types.Header) BlockID {
23 | return NewBlockID(header.Number.Uint64(), header.Hash())
24 | }
25 |
26 | func (id BlockID) GetNumber() uint64 { return id.Number }
27 | func (id BlockID) GetHash() common.Hash { return id.Hash }
28 | func (id BlockID) String() string { return fmt.Sprintf("(#=%d|h=%s)", id.Number, id.Hash) }
29 |
30 | type BlockRef struct {
31 | BlockID
32 | ParentHash common.Hash `json:"parent_hash"`
33 | }
34 |
35 | func NewBlockRef(number uint64, hash common.Hash, parentHash common.Hash) BlockRef {
36 | return BlockRef{NewBlockID(number, hash), parentHash}
37 | }
38 |
39 | func NewBlockRefFromHeader(header *types.Header) BlockRef {
40 | return BlockRef{NewBlockIDFromHeader(header), header.ParentHash}
41 | }
42 |
43 | func (ref BlockRef) GetParentHash() common.Hash { return ref.ParentHash }
44 |
--------------------------------------------------------------------------------
/bindings-go/bindings/L2PortalDeterministicStorage_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const L2PortalDeterministicStorageStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"src/bridge/L2Portal.sol:L2PortalDeterministicStorage\",\"label\":\"initiatedWithdrawals\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_mapping(t_bytes32,t_bool)\"}],\"types\":{\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_mapping(t_bytes32,t_bool)\":{\"encoding\":\"mapping\",\"label\":\"mapping(bytes32 =\u003e bool)\",\"numberOfBytes\":\"32\",\"key\":\"t_bytes32\",\"value\":\"t_bool\"}}}"
13 |
14 | var L2PortalDeterministicStorageStorageLayout = new(solc.StorageLayout)
15 |
16 | var L2PortalDeterministicStorageDeployedBin = "0x"
17 | func init() {
18 | if err := json.Unmarshal([]byte(L2PortalDeterministicStorageStorageLayoutJSON), L2PortalDeterministicStorageStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["L2PortalDeterministicStorage"] = L2PortalDeterministicStorageStorageLayout
23 | deployedBytecodes["L2PortalDeterministicStorage"] = L2PortalDeterministicStorageDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/contracts/src/challenge/verifier/IVerifier.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | import "./VerificationContextLib.sol";
22 |
23 | interface IVerifier {
24 | /**
25 | * @notice Simulates and verifies execution of a single EVM step.
26 | * @param startStateHash The state hash before the step.
27 | * @param ctx Associated transaction and its context (already verified to be consistent).
28 | * @param encodedProof The one-step proof. TODO: describe format.
29 | */
30 | function verifyOneStepProof(
31 | bytes32 startStateHash,
32 | VerificationContextLib.RawContext calldata ctx,
33 | bytes calldata encodedProof
34 | ) external pure returns (bytes32 endStateHash);
35 | }
36 |
--------------------------------------------------------------------------------
/contracts/src/libraries/Hashing.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.4;
3 |
4 | import {Types} from "./Types.sol";
5 | import {Encoding} from "./Encoding.sol";
6 |
7 | /**
8 | * @title Hashing
9 | * @notice Hashing handles Specular's various different hashing schemes.
10 | */
11 | library Hashing {
12 | /**
13 | * @notice Derives the withdrawal hash according to the encoding in the L2 Withdrawer contract
14 | *
15 | * @param _tx Withdrawal transaction to hash.
16 | *
17 | * @return Hashed withdrawal transaction.
18 | */
19 | function hashCrossDomainMessage(Types.CrossDomainMessage memory _tx) internal pure returns (bytes32) {
20 | return keccak256(abi.encode(_tx.nonce, _tx.sender, _tx.target, _tx.value, _tx.gasLimit, _tx.data));
21 | }
22 |
23 | bytes32 public constant STATE_COMMITMENT_V0 = bytes32(0);
24 |
25 | /**
26 | * @notice creates a versioned state commitment
27 | *
28 | * @param l2BlockHash l2 block hash
29 | * @param l2StateRoot l2 state root
30 | */
31 | function createStateCommitmentV0(bytes32 l2BlockHash, bytes32 l2StateRoot) internal pure returns (bytes32) {
32 | // output v0 format is keccak256(version || l2BlockHash || l2StateRoot)
33 | bytes memory stateCommitment = bytes.concat(STATE_COMMITMENT_V0, l2BlockHash, l2StateRoot);
34 | return keccak256(stateCommitment);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/contracts/src/pre-deploy/UUPSPlaceholder.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Modifications Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
22 | import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
23 | import "./PredeployUUPSUpgradeable.sol";
24 |
25 | contract UUPSPlaceholder is PredeployUUPSUpgradeable, OwnableUpgradeable {
26 | /// @custom:oz-upgrades-unsafe-allow constructor
27 | constructor() {
28 | _disableInitializers();
29 | }
30 |
31 | function initialize() public initializer {
32 | __Ownable_init();
33 | __UUPSUpgradeable_init();
34 | }
35 |
36 | function _authorizeUpgrade(address) internal override onlyOwner {}
37 | }
38 |
--------------------------------------------------------------------------------
/packages/sdk/scripts/test_withdrawalERC20.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 | import { ServiceBridge } from "../src/service_bridge";
3 | import { resolve } from "path";
4 | import dotenv from "dotenv";
5 | dotenv.config({ path: resolve(__dirname, "../.env.test") });
6 |
7 | async function main() {
8 | const l1Url: string = process.env.L1_URL!;
9 | const l2Url: string = process.env.L2_URL!;
10 | const l1ChainId: number = parseInt(process.env.L1_CHAIN_ID!);
11 | const l2ChainId: number = parseInt(process.env.L2_CHAIN_ID!);
12 |
13 | const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url);
14 | const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url);
15 | const l1Wallet = new ethers.Wallet(
16 | "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
17 | l1RpcProvider,
18 | );
19 | const l2Wallet = new ethers.Wallet(
20 | "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
21 | l2RpcProvider,
22 | );
23 |
24 | const l1balance = await l1Wallet.getBalance();
25 | console.log("L1 balancee: ", l1balance);
26 |
27 | const serviceBridge = new ServiceBridge({
28 | l1SignerOrProvider: l1Wallet, // l1 signer
29 | l2SignerOrProvider: l2Wallet, // l2 signer
30 | l1ChainId,
31 | l2ChainId,
32 | });
33 | }
34 |
35 | main()
36 | .then(() => process.exit(0))
37 | .catch((error) => {
38 | console.error(error);
39 | process.exit(1);
40 | });
41 |
--------------------------------------------------------------------------------
/packages/sdk/scripts/test_depositERC20.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 | import { formatEther } from "ethers/lib/utils";
3 | import { ServiceBridge } from "../src/service_bridge";
4 | import { resolve } from "path";
5 | import dotenv from "dotenv";
6 | dotenv.config({ path: resolve(__dirname, "../.env.test") });
7 |
8 | async function main() {
9 | const l1Url: string = process.env.L1_URL!;
10 | const l2Url: string = process.env.L2_URL!;
11 | const l1ChainId: number = parseInt(process.env.L1_CHAIN_ID!);
12 | const l2ChainId: number = parseInt(process.env.L2_CHAIN_ID!);
13 |
14 | const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url);
15 | const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url);
16 | const l1Wallet = new ethers.Wallet(
17 | "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
18 | l1RpcProvider,
19 | );
20 | const l2Wallet = new ethers.Wallet(
21 | "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
22 | l2RpcProvider,
23 | );
24 |
25 | const l1balance = await l1Wallet.getBalance();
26 | console.log("L1 balancee: ", l1balance);
27 |
28 | const serviceBridge = new ServiceBridge({
29 | l1SignerOrProvider: l1Wallet, // l1 signer
30 | l2SignerOrProvider: l2Wallet, // l2 signer
31 | l1ChainId,
32 | l2ChainId,
33 | });
34 | }
35 |
36 | main()
37 | .then(() => process.exit(0))
38 | .catch((error) => {
39 | console.error(error);
40 | process.exit(1);
41 | });
42 |
--------------------------------------------------------------------------------
/contracts/Dockerfile:
--------------------------------------------------------------------------------
1 | # Compile contracts for binding generation
2 | FROM node:16-alpine3.17 as contracts-builder
3 |
4 | RUN apk add --no-cache bash git python3 make g++ musl-dev linux-headers && corepack enable
5 |
6 | WORKDIR /specular
7 | COPY pnpm-lock.yaml ./
8 | RUN pnpm fetch
9 |
10 | COPY . /specular
11 | RUN pnpm install --offline --frozen-lockfile
12 |
13 | WORKDIR /specular/clients/geth/specular
14 | RUN make contracts
15 |
16 | # Build geth binary
17 | FROM golang:1.19-alpine3.17 as geth-builder
18 |
19 | RUN apk add --no-cache make gcc musl-dev linux-headers git
20 |
21 | WORKDIR /specular/clients/geth/go-ethereum
22 | COPY --from=contracts-builder /specular/clients/geth/go-ethereum/go.mod ./
23 | COPY --from=contracts-builder /specular/clients/geth/go-ethereum/go.sum ./
24 | RUN go mod download
25 |
26 | WORKDIR /specular/clients/geth/specular
27 | COPY --from=contracts-builder /specular/clients/geth/specular/go.mod ./
28 | COPY --from=contracts-builder /specular/clients/geth/specular/go.sum ./
29 | RUN go mod download
30 |
31 | COPY --from=contracts-builder /specular /specular
32 |
33 | WORKDIR /specular/clients/geth/specular
34 | RUN make geth-docker
35 |
36 | # Specular hardhat image
37 | FROM contracts-builder
38 |
39 | # We need geth for genesis export
40 | COPY --from=geth-builder /specular/clients/geth/specular/build/bin/geth /specular/clients/geth/specular/build/bin/geth
41 |
42 | WORKDIR /specular/contracts
43 |
44 | EXPOSE 8545
45 | CMD ["npx", "hardhat", "node", "--hostname", "0.0.0.0"]
46 |
--------------------------------------------------------------------------------
/.github/workflows/release-snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Release snapshot
2 |
3 | # Releases a snapshot release when new commits merge to develop
4 | # This ensures the release process is working as expected as well as always gives us a release
5 | # we should move to a single branch in near future
6 | on:
7 | workflow_dispatch:
8 | push:
9 | branches:
10 | - develop
11 |
12 | jobs:
13 | snapshot-snapshot:
14 | name: Publish snapshot release to npm
15 | if: github.repository == 'SpecularL2/specular'
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Cache git folder
19 | uses: actions/cache@v3
20 | with:
21 | path: ./.git
22 | key: git-folder
23 |
24 | - name: Checkout
25 | uses: actions/checkout@v4
26 | with:
27 | submodules: recursive
28 | fetch-depth: 0
29 |
30 | - uses: ./.github/actions/node
31 |
32 | - name: Install Foundry
33 | uses: foundry-rs/foundry-toolchain@v1
34 | with:
35 | version: nightly
36 |
37 | - name: Build contracts
38 | run: cd contracts && pnpm build
39 |
40 | - name: Set deployment token
41 | run: npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
42 | env:
43 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
44 |
45 | - name: Publish snapshots
46 | uses: seek-oss/changesets-snapshot@v0
47 | env:
48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
50 |
--------------------------------------------------------------------------------
/services/sidecar/utils/retry/operation.go:
--------------------------------------------------------------------------------
1 | package retry
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/specularL2/specular/services/sidecar/utils/fmt"
8 | )
9 |
10 | // ErrFailedPermanently is an error raised by Do when the
11 | // underlying Operation has been retried maxAttempts times.
12 | type ErrFailedPermanently struct {
13 | attempts int
14 | LastErr error
15 | }
16 |
17 | func (e *ErrFailedPermanently) Error() string {
18 | return fmt.Sprintf("operation failed permanently after %d attempts: %v", e.attempts, e.LastErr)
19 | }
20 |
21 | func (e *ErrFailedPermanently) Unwrap() error {
22 | return e.LastErr
23 | }
24 |
25 | // Do performs the provided Operation up to maxAttempts times
26 | // with delays in between each retry according to the provided
27 | // Strategy.
28 | func Do[T any](ctx context.Context, maxAttempts int, strategy Strategy, op func() (T, error)) (T, error) {
29 | var empty, ret T
30 | var err error
31 | if maxAttempts < 1 {
32 | return empty, fmt.Errorf("need at least 1 attempt to run op, but have %d max attempts", maxAttempts)
33 | }
34 |
35 | for i := 0; i < maxAttempts; i++ {
36 | if ctx.Err() != nil {
37 | return empty, ctx.Err()
38 | }
39 | ret, err = op()
40 | if err == nil {
41 | return ret, nil
42 | }
43 | // Don't sleep when we are about to exit the loop & return ErrFailedPermanently
44 | if i != maxAttempts-1 {
45 | time.Sleep(strategy.Duration(i))
46 | }
47 | }
48 | return empty, &ErrFailedPermanently{
49 | attempts: maxAttempts,
50 | LastErr: err,
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/services/sidecar/proof/proof/proof.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022, Specular contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package proof
16 |
17 | type Proof interface {
18 | Encode() []byte
19 | }
20 |
21 | type OneStepProof struct {
22 | Proofs []Proof
23 | }
24 |
25 | func EmptyProof() *OneStepProof {
26 | return &OneStepProof{}
27 | }
28 |
29 | func (p *OneStepProof) AddProof(proof Proof) {
30 | p.Proofs = append(p.Proofs, proof)
31 | }
32 |
33 | func (p *OneStepProof) Encode() []byte {
34 | if len(p.Proofs) == 0 {
35 | // Empty proof!
36 | return []byte{}
37 | }
38 | encodedLen := 0
39 | encodedProofs := make([][]byte, len(p.Proofs))
40 | for idx, proof := range p.Proofs {
41 | encodedProofs[idx] = proof.Encode()
42 | encodedLen += len(encodedProofs[idx])
43 | }
44 | encoded := make([]byte, encodedLen)
45 | offset := 0
46 | for _, encodedProof := range encodedProofs {
47 | copy(encoded[offset:], encodedProof)
48 | offset += len(encodedProof)
49 | }
50 | return encoded
51 | }
52 |
--------------------------------------------------------------------------------
/services/sidecar/README.md:
--------------------------------------------------------------------------------
1 | # Specular Sidecar
2 |
3 | ## License
4 |
5 | This module is licensed under [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0); see `LICENSE` for details.
6 |
7 | ## Development
8 |
9 | ### Configuration
10 |
11 | Sidecar supports configuration injection via [Viper](https://github.com/spf13/viper) (`.json`, `.toml`, `.yaml`, `.env`
12 | and environment variables) autoload from the current working directory and `config` folder.
13 |
14 | It additionally uses [CLI](https://github.com/urfave/cli) for the command line arguments parser.
15 |
16 | Currently, config is split between `Config` and `SystemConfig`, consolidation of the configuration is WIP.
17 |
18 | ### Using Wire
19 |
20 | Path `internal/service/di` contains providers injected using `inject.go`.
21 | Wire will automatically match demand of the arguments in the constructors injected
22 | with the provided constructors of objects. The initiation order will be determined automatically.
23 | After generation `wire_gen.go` file will be generated containing
24 | `func SetupApplication() (*Application, func(), error)` for `main` and
25 | `SetupApplicationForIntegrationTests(cfg *config.Config) (*TestApplication, func(), error)`
26 | for the testing purposes.
27 |
28 | After modifying providers, you need to call `make wire-generate` in order to regenerate.
29 |
30 | Object instances are directly assigned to `Application` object fields and ready to access within
31 | the `Run()` entry point function.
32 |
33 | For details please refer to [Wire's official documentation](https://github.com/google/wire).
34 |
--------------------------------------------------------------------------------
/ops/predeploys/addresses.go:
--------------------------------------------------------------------------------
1 | package predeploys
2 |
3 | import "github.com/ethereum/go-ethereum/common"
4 |
5 | // TODO: This list should be generated from a configuration source
6 | const (
7 | UUPSPlaceholder = "0x2A00000000000000000000000000000000000000"
8 | L1Oracle = "0x2A00000000000000000000000000000000000010"
9 | L2Portal = "0x2A00000000000000000000000000000000000011"
10 | L2StandardBridge = "0x2A00000000000000000000000000000000000012"
11 | L1FeeVault = "0x2A00000000000000000000000000000000000020"
12 | L2BaseFeeVault = "0x2A00000000000000000000000000000000000021"
13 | MintableERC20Factory = "0x2A000000000000000000000000000000000000f0"
14 | )
15 |
16 | var (
17 | UUPSPlaceholderAddr = common.HexToAddress(UUPSPlaceholder)
18 | L1OracleAddr = common.HexToAddress(L1Oracle)
19 | L2PortalAddr = common.HexToAddress(L2Portal)
20 | L2StandardBridgeAddr = common.HexToAddress(L2StandardBridge)
21 | L1FeeVaultAddr = common.HexToAddress(L1FeeVault)
22 | L2BaseFeeVaultAddr = common.HexToAddress(L2BaseFeeVault)
23 | MintableERC20FactoryAddr = common.HexToAddress(MintableERC20Factory)
24 |
25 | Predeploys = make(map[string]*common.Address)
26 | )
27 |
28 | func init() {
29 | Predeploys["UUPSPlaceholder"] = &UUPSPlaceholderAddr
30 | Predeploys["L1Oracle"] = &L1OracleAddr
31 | Predeploys["L2Portal"] = &L2PortalAddr
32 | Predeploys["L2StandardBridge"] = &L2StandardBridgeAddr
33 | Predeploys["L1FeeVault"] = &L1FeeVaultAddr
34 | Predeploys["L2BaseFeeVault"] = &L2BaseFeeVaultAddr
35 | Predeploys["MintableERC20Factory"] = &MintableERC20FactoryAddr
36 | }
37 |
--------------------------------------------------------------------------------
/sbin/start_sidecar.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # the local sbin paths are relative to the project root
4 | SBIN=$(dirname "$(readlink -f "$0")")
5 | SBIN="$(
6 | cd "$SBIN"
7 | pwd
8 | )"
9 | . $SBIN/utils/utils.sh
10 | ROOT_DIR=$SBIN/..
11 |
12 | # Check that the all required dotenv files exists.
13 | reqdotenv "paths" ".paths.env"
14 | reqdotenv "sidecar" ".sidecar.env"
15 |
16 | FLAGS=(
17 | "--l1.endpoint $L1_ENDPOINT"
18 | "--l2.endpoint $L2_ENDPOINT"
19 | "--protocol.rollup-cfg-path $ROLLUP_CFG_PATH"
20 | )
21 |
22 | # Set disseminator flags.
23 | if [ "$DISSEMINATOR" = true ]; then
24 | echo "Enabling disseminator."
25 | DISSEMINATOR_PRIV_KEY=$(cat "$DISSEMINATOR_PK_PATH")
26 | FLAGS+=(
27 | "--disseminator"
28 | "--disseminator.private-key $DISSEMINATOR_PRIV_KEY"
29 | "--disseminator.sub-safety-margin $DISSEMINATOR_SUB_SAFETY_MARGIN"
30 | "--disseminator.target-batch-size $DISSEMINATOR_TARGET_BATCH_SIZE"
31 | "--disseminator.max-batch-size $DISSEMINATOR_MAX_BATCH_SIZE"
32 | "--disseminator.max-safe-lag $DISSEMINATOR_MAX_SAFE_LAG"
33 | "--disseminator.max-safe-lag-delta $DISSEMINATOR_MAX_SAFE_LAG_DELTA"
34 | )
35 | if [ -n "$DISSEMINATOR_INTERVAL" ]; then
36 | FLAGS+=("--disseminator.interval $DISSEMINATOR_INTERVAL")
37 | fi
38 | fi
39 | # Set validator flags.
40 | if [ "$VALIDATOR" = true ]; then
41 | echo "Enabling validator."
42 | VALIDATOR_PRIV_KEY=$(cat "$VALIDATOR_PK_PATH")
43 | FLAGS+=(
44 | "--validator"
45 | "--validator.private-key $VALIDATOR_PRIV_KEY"
46 | )
47 | fi
48 |
49 | echo "starting sidecar with the following flags:"
50 | echo "${FLAGS[@]}"
51 | $SIDECAR_BIN ${FLAGS[@]}
52 |
--------------------------------------------------------------------------------
/contracts/src/libraries/DeserializationLib.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | import "./BytesLib.sol";
22 |
23 | library DeserializationLib {
24 | function deserializeAddress(bytes memory data, uint256 startOffset) internal pure returns (uint256, address) {
25 | return (startOffset + 20, BytesLib.toAddress(data, startOffset));
26 | }
27 |
28 | function deserializeUint256(bytes memory data, uint256 startOffset) internal pure returns (uint256, uint256) {
29 | require(data.length >= startOffset && data.length - startOffset >= 32, "too short");
30 | return (startOffset + 32, BytesLib.toUint256(data, startOffset));
31 | }
32 |
33 | function deserializeBytes32(bytes memory data, uint256 startOffset) internal pure returns (uint256, bytes32) {
34 | require(data.length >= startOffset && data.length - startOffset >= 32, "too short");
35 | return (startOffset + 32, BytesLib.toBytes32(data, startOffset));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/contracts/src/libraries/Errors.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | /// @dev Thrown when unauthorized (!rollup) address calls an only-rollup function
22 | /// @param sender Address of the caller
23 | /// @param rollup The rollup address authorized to call this function
24 | error NotRollup(address sender, address rollup);
25 |
26 | /// @dev Thrown when unauthorized (!challenge) address calls an only-challenge function
27 | /// @param sender Address of the caller
28 | /// @param challenge The challenge address authorized to call this function
29 | error NotChallengeManager(address sender, address challenge);
30 |
31 | /// @dev Thrown when unauthorized (!sequencer) address calls an only-sequencer function
32 | /// @param sender Address of the caller
33 | /// @param sequencer The sequencer address authorized to call this function
34 | error NotSequencer(address sender, address sequencer);
35 |
36 | /// @dev Thrown when function is called with a zero address argument
37 | error ZeroAddress();
38 |
--------------------------------------------------------------------------------
/charts/specular/templates/debug-pod.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.enabled.debug -}}
2 | apiVersion: v1
3 | kind: Pod
4 | metadata:
5 | name: debug-container
6 | namespace: specular
7 | spec:
8 | # initContainers:
9 | # - name: wait-for-sp-geth
10 | # image: busybox:latest
11 | # command: ["sh", "-c", "echo '{{ .Values.image.tag }}' > /tmp/release"]
12 | # volumeMounts:
13 | # - mountPath: /tmp
14 | # name: {{ .Values.volume.efs.name }}
15 | containers:
16 | - name: debug-container
17 | image: nginx
18 | volumeMounts:
19 | - mountPath: /tmp/workspace
20 | name: {{ .Values.volume.efs.name }}
21 | {{- range $i, $value := .Values.volume.configVolumeMounts.paths }}
22 | - name: {{ $.Values.volume.configVolumeMounts.name }}
23 | mountPath: /tmp/workspace/{{ $value }}
24 | subPath: {{ $value }}
25 | {{- end }}
26 | {{- if not .Values.generator.deploy }}
27 | {{- range $i, $value := .Values.volume.secrets }}
28 | - name: secret-volume
29 | mountPath: /tmp/workspace/{{ $value.file }}
30 | subPath: {{ $value.file }}
31 | readOnly: true
32 | {{- end }}
33 | {{- end }}
34 | workingDir: /tmp/workspace
35 | restartPolicy: OnFailure
36 | volumes:
37 | - name: {{ .Values.volume.efs.name }}
38 | persistentVolumeClaim:
39 | claimName: {{ .Values.volume.efs.name }}
40 | - name: {{ .Values.volume.configVolumeMounts.name }}
41 | configMap:
42 | name: {{ .Values.volume.configVolumeMounts.name }}
43 | {{- if not .Values.generator.deploy }}
44 | - name: secret-volume
45 | secret:
46 | secretName: l2-secrets
47 | {{- end }}
48 | status: {}
49 | {{- end -}}
50 |
--------------------------------------------------------------------------------
/charts/specular/templates/generator-pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | creationTimestamp: null
5 | labels:
6 | io.specular.network/sp-network: "true"
7 | io.specular.service: generator
8 | name: generator
9 | spec:
10 | containers:
11 | - name: generator
12 | command: ["bash", "-c", "{{ .Values.generator.command }}"]
13 | image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
14 | resources:
15 | {{- .Values.default_resources | toYaml | nindent 10 }}
16 | volumeMounts:
17 | - mountPath: {{ .Values.volume.efs.mountPath }}
18 | name: {{ .Values.volume.efs.name }}
19 | {{- range $i, $value := .Values.volume.configVolumeMounts.paths }}
20 | - name: {{ $.Values.volume.configVolumeMounts.name }}
21 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value }}
22 | subPath: {{ $value }}
23 | {{- end }}
24 | {{- if not .Values.generator.deploy }}
25 | {{- range $i, $value := .Values.volume.secrets }}
26 | - name: secret-volume
27 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value.file }}
28 | subPath: {{ $value.file }}
29 | readOnly: true
30 | {{- end }}
31 | {{- end }}
32 | restartPolicy: Never
33 | volumes:
34 | - name: {{ .Values.volume.efs.name }}
35 | persistentVolumeClaim:
36 | claimName: {{ .Values.volume.efs.name }}
37 | - name: {{ .Values.volume.configVolumeMounts.name }}
38 | configMap:
39 | name: {{ .Values.volume.configVolumeMounts.name }}
40 | {{- if not .Values.generator.deploy }}
41 | - name: secret-volume
42 | secret:
43 | secretName: l2-secrets
44 | {{- end }}
45 | status: {}
46 |
--------------------------------------------------------------------------------
/contracts/scripts/config/set_rollup_genesis_state.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import fs from "fs";
3 | import { parseFlag } from "./utils";
4 | import hre from "hardhat";
5 |
6 | const { ethers } = hre;
7 |
8 | const CONTRACTS_DIR = path.join(__dirname, "/../../");
9 | require("dotenv").config({ path: path.join(CONTRACTS_DIR, ".genesis.env") });
10 | const EXPORTED_PATH = process.env.GENESIS_EXPORTED_HASH_PATH || "";
11 | const ROLLUP_ADDR = process.env.ROLLUP_ADDR || "invalid address";
12 |
13 | async function main() {
14 | const exported = require(path.join(CONTRACTS_DIR, EXPORTED_PATH));
15 | console.log({ exported });
16 | const initialBlockHash = (exported.hash || "") as string;
17 | if (!initialBlockHash) {
18 | throw Error(`blockHash not found\n$`);
19 | }
20 | console.log("initial blockHash:", initialBlockHash);
21 |
22 | const initialStateRoot = (exported.stateRoot || "") as string;
23 | if (!initialStateRoot) {
24 | throw Error(`stateRoot not found\n$`);
25 | }
26 | console.log("initial stateRoot:", initialStateRoot);
27 |
28 | const initialRollupState = {
29 | assertionID: 0,
30 | l2BlockNum: 0,
31 | l2BlockHash: initialBlockHash,
32 | l2StateRoot: initialStateRoot,
33 | };
34 |
35 | const { deployer } = await hre.getNamedAccounts();
36 | const RollupFactory = await ethers.getContractFactory("Rollup", deployer);
37 | const rollup = RollupFactory.attach(ROLLUP_ADDR);
38 | const tx = await rollup.initializeGenesis(initialRollupState);
39 | await tx.wait();
40 | console.log("initialized genesis state on rollup contract");
41 | }
42 |
43 | if (!require.main!.loaded) {
44 | main().catch((error) => {
45 | console.error(error);
46 | process.exitCode = 1;
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/bindings-go/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/specularL2/specular/bindings-go
2 |
3 | go 1.20
4 |
5 | replace github.com/ethereum/go-ethereum => ../services/el_clients/go-ethereum
6 |
7 | require github.com/ethereum/go-ethereum v1.13.2
8 |
9 | require (
10 | github.com/Microsoft/go-winio v0.6.1 // indirect
11 | github.com/StackExchange/wmi v1.2.1 // indirect
12 | github.com/bits-and-blooms/bitset v1.5.0 // indirect
13 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
14 | github.com/consensys/bavard v0.1.13 // indirect
15 | github.com/consensys/gnark-crypto v0.10.0 // indirect
16 | github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect
17 | github.com/deckarep/golang-set/v2 v2.1.0 // indirect
18 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
19 | github.com/ethereum/c-kzg-4844 v0.3.1 // indirect
20 | github.com/fsnotify/fsnotify v1.7.0 // indirect
21 | github.com/go-ole/go-ole v1.2.5 // indirect
22 | github.com/go-stack/stack v1.8.1 // indirect
23 | github.com/google/uuid v1.3.0 // indirect
24 | github.com/gorilla/websocket v1.4.2 // indirect
25 | github.com/holiman/uint256 v1.2.3 // indirect
26 | github.com/mmcloughlin/addchain v0.4.0 // indirect
27 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
28 | github.com/supranational/blst v0.3.11 // indirect
29 | github.com/tklauser/go-sysconf v0.3.12 // indirect
30 | github.com/tklauser/numcpus v0.6.1 // indirect
31 | golang.org/x/crypto v0.12.0 // indirect
32 | golang.org/x/exp v0.0.0-20230810033253-352e893a4cad // indirect
33 | golang.org/x/mod v0.11.0 // indirect
34 | golang.org/x/sync v0.3.0 // indirect
35 | golang.org/x/sys v0.11.0 // indirect
36 | golang.org/x/tools v0.9.1 // indirect
37 | rsc.io/tmplfunc v0.0.3 // indirect
38 | )
39 |
--------------------------------------------------------------------------------
/contracts/src/challenge/verifier/Verifier.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | import "./IVerifier.sol";
22 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
23 | import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
24 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
25 |
26 | contract Verifier is IVerifier, Initializable, UUPSUpgradeable, OwnableUpgradeable {
27 | function initialize() public initializer {
28 | __Ownable_init();
29 | __UUPSUpgradeable_init();
30 | }
31 |
32 | /// @custom:oz-upgrades-unsafe-allow constructor
33 | constructor() {
34 | _disableInitializers();
35 | }
36 |
37 | function _authorizeUpgrade(address) internal override onlyOwner {}
38 |
39 | function verifyOneStepProof(bytes32, VerificationContextLib.RawContext calldata, bytes calldata)
40 | external
41 | pure
42 | override
43 | returns (bytes32 endStateHash)
44 | {
45 | endStateHash = 0x0;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/services/sidecar/utils/retry/strategy.go:
--------------------------------------------------------------------------------
1 | package retry
2 |
3 | import (
4 | "math"
5 | "math/rand"
6 | "time"
7 | )
8 |
9 | // Strategy is used to calculate how long a particular Operation
10 | // should wait between attempts.
11 | type Strategy interface {
12 | // Duration returns how long to wait for a given retry attempt.
13 | Duration(attempt int) time.Duration
14 | }
15 |
16 | // ExponentialStrategy performs exponential backoff. The exponential backoff
17 | // function is min(e.Min + (2^attempt * 1000) + randBetween(0, e.MaxJitter), e.Max)
18 | type ExponentialStrategy struct {
19 | // Min is the minimum amount of time to wait between attempts.
20 | Min time.Duration
21 |
22 | // Max is the maximum amount of time to wait between attempts.
23 | Max time.Duration
24 |
25 | // MaxJitter is the maximum amount of random jitter to insert between attempts.
26 | MaxJitter time.Duration
27 | }
28 |
29 | func (e *ExponentialStrategy) Duration(attempt int) time.Duration {
30 | var jitter time.Duration
31 | if e.MaxJitter > 0 {
32 | jitter = time.Duration(rand.Int63n(e.MaxJitter.Nanoseconds()))
33 | }
34 | dur := e.Min + time.Duration(int(math.Pow(2, float64(attempt))*1000))*time.Millisecond
35 | dur += jitter
36 | if dur > e.Max {
37 | return e.Max
38 | }
39 |
40 | return dur
41 | }
42 |
43 | func Exponential() Strategy {
44 | return &ExponentialStrategy{
45 | Max: time.Duration(10000 * time.Millisecond),
46 | MaxJitter: time.Duration(250 * time.Millisecond),
47 | }
48 | }
49 |
50 | type FixedStrategy struct {
51 | Dur time.Duration
52 | }
53 |
54 | func (f *FixedStrategy) Duration(attempt int) time.Duration {
55 | return f.Dur
56 | }
57 |
58 | func Fixed(dur time.Duration) Strategy {
59 | return &FixedStrategy{
60 | Dur: dur,
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/sdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@specularl2/sdk",
3 | "version": "0.0.1",
4 | "description": "Tools for interacting with Specular",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "files": [
8 | "dist/**/*"
9 | ],
10 | "scripts": {
11 | "prettier": "prettier --write .",
12 | "lint": "eslint . --fix",
13 | "build": "tsc --build",
14 | "gen:binding": "typechain --target ethers-v5 --out-dir src/types/contracts \"./node_modules/@specularl2/contracts/artifacts/src/**/*[!dbg].json\"",
15 | "preinstall": "npx only-allow pnpm",
16 | "prepublishOnly": "pnpm run gen:binding && pnpm run build",
17 | "test:deposit": "ts-node scripts/test_deposit.ts",
18 | "test:depositERC": "ts-node scripts/test_depositeERC20.ts",
19 | "test:withdraw": "ts-node scripts/test_withdrawal.ts",
20 | "test:withdrawERC": "ts-node scripts/test_withdrawalERC20.ts"
21 | },
22 | "keywords": [
23 | "specular",
24 | "ethereum",
25 | "sdk"
26 | ],
27 | "author": "Specular contributors",
28 | "license": "Apache-2.0",
29 | "devDependencies": {
30 | "@specularl2/contracts": "workspace:*",
31 | "@types/lodash": "^4.14.202",
32 | "eslint": "^8.56.0",
33 | "prettier": "^3.1.1",
34 | "typechain": "^8.1.1",
35 | "typescript": "^4.9.3"
36 | },
37 | "dependencies": {
38 | "@ethersproject/abi": "^5.7.0",
39 | "@ethersproject/abstract-provider": "^5.7.0",
40 | "@ethersproject/abstract-signer": "^5.7.0",
41 | "@ethersproject/bytes": "^5.7.0",
42 | "@ethersproject/providers": "^5.7.2",
43 | "@ethersproject/transactions": "^5.7.0",
44 | "@openzeppelin/contracts": "^4.9.5",
45 | "dotenv": "^16.4.5",
46 | "ethers": "^5.7.2",
47 | "mocha": "^10.2.0",
48 | "vitest": "^1.1.3"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/bindings-go/bindings/registry.go:
--------------------------------------------------------------------------------
1 | package bindings
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/ethereum/go-ethereum/common"
8 | "github.com/specularL2/specular/bindings-go/solc"
9 | )
10 |
11 | // layouts respresents the set of storage layouts. It is populated in an init function.
12 | var layouts = make(map[string]*solc.StorageLayout)
13 |
14 | // deployedBytecodes represents the set of deployed bytecodes. It is populated
15 | // in an init function.
16 | var deployedBytecodes = make(map[string]string)
17 |
18 | // GetStorageLayout returns the storage layout of a contract by name.
19 | func GetStorageLayout(name string) (*solc.StorageLayout, error) {
20 | layout := layouts[name]
21 | if layout == nil {
22 | return nil, fmt.Errorf("%s: storage layout not found", name)
23 | }
24 | return layout, nil
25 | }
26 |
27 | // GetDeployedBytecode returns the deployed bytecode of a contract by name.
28 | func GetDeployedBytecode(name string) ([]byte, error) {
29 | bc := deployedBytecodes[name]
30 | if bc == "" {
31 | return nil, fmt.Errorf("%s: deployed bytecode not found", name)
32 | }
33 |
34 | if !isHex(bc) {
35 | return nil, fmt.Errorf("%s: invalid deployed bytecode", name)
36 | }
37 |
38 | return common.FromHex(bc), nil
39 | }
40 |
41 | // isHexCharacter returns bool of c being a valid hexadecimal.
42 | func isHexCharacter(c byte) bool {
43 | return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
44 | }
45 |
46 | // isHex validates whether each byte is valid hexadecimal string.
47 | func isHex(str string) bool {
48 | if len(str)%2 != 0 {
49 | return false
50 | }
51 | str = strings.TrimPrefix(str, "0x")
52 |
53 | for _, c := range []byte(str) {
54 | if !isHexCharacter(c) {
55 | return false
56 | }
57 | }
58 | return true
59 | }
60 |
--------------------------------------------------------------------------------
/contracts/src/libraries/Encoding.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import {Types} from "./Types.sol";
5 | import {Hashing} from "./Hashing.sol";
6 | import {RLPReader} from "./rlp/RLPReader.sol";
7 |
8 | library Encoding {
9 | /**
10 | * @notice Decode the state root from an encoded block header.
11 | *
12 | * @param encodedBlockHeader Address to call on L2 execution.
13 | * @return blockHash The block hash.
14 | * @return stateRoot The state root.
15 | */
16 | function decodeStateRootFromEncodedBlockHeader(bytes memory encodedBlockHeader)
17 | internal
18 | pure
19 | returns (bytes32, bytes32)
20 | {
21 | RLPReader.RLPItem memory item = RLPReader.toRlpItem(encodedBlockHeader);
22 | RLPReader.RLPItem[] memory blockHeader = RLPReader.toList(item);
23 | bytes32 blockHash = keccak256(encodedBlockHeader);
24 | // stateRoot is the 4th element in the block header
25 | bytes32 stateRoot = bytes32(RLPReader.toUintStrict(blockHeader[3]));
26 | return (blockHash, stateRoot);
27 | }
28 |
29 | /**
30 | * @notice Decode the storage root from an encoded account.
31 | *
32 | * @param encodedAccount Address to call on L2 execution.
33 | * @return storageRoot The storage root.
34 | */
35 | function decodeStorageRootFromEncodedAccount(bytes memory encodedAccount) internal pure returns (bytes32) {
36 | RLPReader.RLPItem memory item = RLPReader.toRlpItem(encodedAccount);
37 | RLPReader.RLPItem[] memory account = RLPReader.toList(item);
38 | // storageRoot is the 3rd element in the account
39 | bytes32 storageRoot = bytes32(RLPReader.toUintStrict(account[2]));
40 | return storageRoot;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/onboarding/src/onboarding/messenger.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber, Signer } from "ethers";
2 | import { NonceManager } from "@ethersproject/experimental";
3 |
4 | import {
5 | IL2Portal,
6 | IL2Portal__factory,
7 | L1Oracle,
8 | L1Oracle__factory,
9 | l1OracleAddress,
10 | l2PortalAddress,
11 | } from "@specularl2/sdk";
12 | import { CrossDomainMessage } from ".";
13 |
14 | export type CrossDomainMessagerConfig = {
15 | l2Funder: Signer;
16 | };
17 |
18 | export type MessageProof = {
19 | accountProof: string[];
20 | storageProof: string[];
21 | };
22 |
23 | export class CrossDomainMessenger {
24 | readonly config: CrossDomainMessagerConfig;
25 |
26 | readonly l2Funder: NonceManager;
27 |
28 | readonly l1Oracle: L1Oracle;
29 | readonly l2Portal: IL2Portal;
30 |
31 | static async create(config: CrossDomainMessagerConfig) {
32 | const l2Funder = new NonceManager(config.l2Funder);
33 | return new CrossDomainMessenger(config, new NonceManager(l2Funder));
34 | }
35 |
36 | constructor(config: CrossDomainMessagerConfig, l2Funder: NonceManager) {
37 | this.config = config;
38 |
39 | this.l2Funder = l2Funder;
40 |
41 | this.l1Oracle = L1Oracle__factory.connect(l1OracleAddress, this.l2Funder);
42 | this.l2Portal = IL2Portal__factory.connect(l2PortalAddress, this.l2Funder);
43 | }
44 |
45 | async getL1OracleBlockNumber() {
46 | return await this.l1Oracle.number();
47 | }
48 |
49 | async finalizeDeposit(
50 | l1BlockNumber: BigNumber,
51 | depositTx: CrossDomainMessage,
52 | depositProof: MessageProof
53 | ) {
54 | const tx = await this.l2Portal.finalizeDepositTransaction(
55 | l1BlockNumber,
56 | depositTx,
57 | depositProof.accountProof,
58 | depositProof.storageProof
59 | );
60 | return await tx.wait();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/contracts/scripts/config/create_deployments_config.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import { parseFlag } from "./utils";
3 |
4 | require("dotenv").config();
5 |
6 | // TODO: consider moving to golang (ops).
7 | async function main() {
8 | const deploymentsConfig = parseFlag("--deployments-config-path");
9 | const deploymentsPath = parseFlag("--deployments", "./deployments/localhost");
10 | await generateContractAddresses(deploymentsConfig, deploymentsPath);
11 | }
12 |
13 | /**
14 | * Reads the L1 deployment and writes deployments address to the deployments env file
15 | */
16 | export async function generateContractAddresses(
17 | deploymentsConfigPath: string,
18 | deploymentsPath: string,
19 | ) {
20 | // check the deployments dir - error out if it is not there
21 | const deploymentFiles = fs.readdirSync(deploymentsPath);
22 | let result = "";
23 | for (const deploymentFile of deploymentFiles) {
24 | if (
25 | deploymentFile.startsWith("Proxy__") &&
26 | deploymentFile.endsWith(".json")
27 | ) {
28 | const deployment = JSON.parse(
29 | fs.readFileSync(`${deploymentsPath}/${deploymentFile}`, "utf-8"),
30 | );
31 | let contractName = deploymentFile
32 | .replace(/^Proxy__/, "")
33 | .replace(/\.json$/, "");
34 | contractName = contractName
35 | .replace(/([a-z])([A-Z])/g, "$1_$2")
36 | .toUpperCase();
37 | result += `${contractName}_ADDR=${deployment.address}\n`;
38 | result += `${contractName}_DEPLOYED_BLOCK=${deployment.receipt.blockNumber}\n`;
39 | }
40 | }
41 | fs.writeFileSync(deploymentsConfigPath, result);
42 | console.log(`successfully wrote deployments to: ${deploymentsConfigPath}`);
43 | }
44 |
45 | if (!require.main!.loaded) {
46 | main().catch((error) => {
47 | console.error(error);
48 | process.exitCode = 1;
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/sbin/start_sp_magi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # the local sbin paths are relative to the project root
5 | SBIN=$(dirname "$(readlink -f "$0")")
6 | SBIN="$(
7 | cd "$SBIN"
8 | pwd
9 | )"
10 | . $SBIN/utils/utils.sh
11 | ROOT_DIR=$SBIN/..
12 |
13 | # Check that the all required dotenv files exists.
14 | reqdotenv "paths" ".paths.env"
15 | reqdotenv "sp_magi" ".sp_magi.env"
16 |
17 | # Generate waitfile for service init (docker/k8)
18 | WAITFILE="/tmp/.${0##*/}.lock"
19 |
20 | if [[ ! -z ${WAIT_DIR+x} ]]; then
21 | WAITFILE=$WAIT_DIR/.${0##*/}.lock
22 | fi
23 |
24 | # Set sync flags.
25 | SYNC_FLAGS=""
26 | if [ $SYNC_MODE = "checkpoint" ]; then
27 | SYNC_FLAGS="--checkpoint-sync-url $CHECKPOINT_SYNC_URL --checkpoint-hash $CHECKPOINT_HASH"
28 | fi
29 |
30 | # Set devnet flags.
31 | DEVNET_FLAGS=""
32 | if [ "$DEVNET" = true ]; then
33 | echo "Enabling devnet mode."
34 | DEVNET_FLAGS="--devnet"
35 | fi
36 |
37 | # Set local sequencer flags.
38 | SEQUENCER_FLAGS=""
39 | if [ "$SEQUENCER" = true ]; then
40 | echo "Enabling local sequencer."
41 | SEQUENCER_FLAGS="
42 | --sequencer \
43 | --sequencer-max-safe-lag $SEQUENCER_MAX_SAFE_LAG \
44 | --sequencer-pk-file $SEQUENCER_PK_FILE"
45 | fi
46 |
47 | # TODO: use array for flags
48 | FLAGS="
49 | --network $NETWORK \
50 | --l1-rpc-url $L1_RPC_URL \
51 | --l2-rpc-url $L2_RPC_URL \
52 | --sync-mode $SYNC_MODE \
53 | --l2-engine-url $L2_ENGINE_URL \
54 | --jwt-file $JWT_SECRET_PATH \
55 | --rpc-port $RPC_PORT \
56 | --watcher-delay $WATCHER_DELAY \
57 | $SYNC_FLAGS $DEVNET_FLAGS $SEQUENCER_FLAGS $@"
58 |
59 | echo "starting sp-magi with the following flags:"
60 | echo "$FLAGS"
61 |
62 | $SP_MAGI_BIN $FLAGS &
63 |
64 | PID=$!
65 | echo "PID: $PID"
66 |
67 | echo "Creating wait file for docker at $WAITFILE..."
68 | touch $WAITFILE
69 |
70 | wait $PID
71 |
--------------------------------------------------------------------------------
/services/sidecar/proof/prover/debug_prover.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022, Specular contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package prover
16 |
17 | import (
18 | "math/big"
19 |
20 | "github.com/ethereum/go-ethereum/common"
21 | "github.com/ethereum/go-ethereum/core/vm"
22 | )
23 |
24 | type DebugProver struct{}
25 |
26 | func NewDebugProver() *DebugProver {
27 | return &DebugProver{}
28 | }
29 |
30 | func (l *DebugProver) CaptureTxStart(gasLimit uint64) {
31 |
32 | }
33 |
34 | func (l *DebugProver) CaptureTxEnd(restGas uint64) {
35 |
36 | }
37 |
38 | func (l *DebugProver) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
39 | }
40 |
41 | func (l *DebugProver) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
42 | }
43 |
44 | func (l *DebugProver) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
45 | }
46 |
47 | func (l *DebugProver) CaptureExit(output []byte, gasUsed uint64, err error) {}
48 |
49 | func (l *DebugProver) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
50 | }
51 |
52 | func (l *DebugProver) CaptureEnd(output []byte, gasUsed uint64, err error) {}
53 |
--------------------------------------------------------------------------------
/ops/predeploys/helpers.go:
--------------------------------------------------------------------------------
1 | package predeploys
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "math/big"
7 |
8 | "github.com/ethereum/go-ethereum/common"
9 | )
10 |
11 | var (
12 | // codeNamespace represents the namespace of implementations of predeploys
13 | codeNamespace = common.HexToAddress("0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000")
14 | // l2PredeployNamespace represents the namespace of L2 predeploys
15 | l2PredeployNamespace = common.HexToAddress("0x2A00000000000000000000000000000000000000")
16 | // bigL2PredeployNamespace represents the predeploy namespace as a big.Int
17 | BigL2PredeployNamespace = new(big.Int).SetBytes(l2PredeployNamespace.Bytes())
18 | // bigCodeNamespace represents the predeploy namespace as a big.Int
19 | bigCodeNameSpace = new(big.Int).SetBytes(codeNamespace.Bytes())
20 | // implementationSlot represents the EIP 1967 implementation storage slot
21 | ImplementationSlot = common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
22 | // adminSlot represents the EIP 1967 admin storage slot
23 | AdminSlot = common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
24 | // predeployProxyCount represents the number of predeploy proxies in the namespace
25 | PredeployProxyCount uint64 = 2048
26 | )
27 |
28 | // AddressToCodeNamespace takes a predeploy address and computes
29 | // the implementation address that the implementation should be deployed at
30 | func AddressToCodeNamespace(addr common.Address) (common.Address, error) {
31 | if !IsL2Predeploy(addr) {
32 | return common.Address{}, fmt.Errorf("cannot handle non predeploy: %s", addr)
33 | }
34 | bigAddress := new(big.Int).SetBytes(addr[18:])
35 | num := new(big.Int).Or(bigCodeNameSpace, bigAddress)
36 | return common.BigToAddress(num), nil
37 | }
38 |
39 | func IsL2Predeploy(addr common.Address) bool {
40 | return bytes.Equal(addr[0:2], []byte{0x2A, 0x00})
41 | }
42 |
--------------------------------------------------------------------------------
/contracts/src/vendor/AddressAliasHelper.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2019-2021, Offchain Labs, Inc.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | library AddressAliasHelper {
22 | uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
23 |
24 | /// @notice Utility function that converts the address in the L1 that submitted a tx to
25 | /// the inbox to the msg.sender viewed in the L2
26 | /// @param l1Address the address in the L1 that triggered the tx to L2
27 | /// @return l2Address L2 address as viewed in msg.sender
28 | function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
29 | unchecked {
30 | l2Address = address(uint160(l1Address) + offset);
31 | }
32 | }
33 |
34 | /// @notice Utility function that converts the msg.sender viewed in the L2 to the
35 | /// address in the L1 that submitted a tx to the inbox
36 | /// @param l2Address L2 address as viewed in msg.sender
37 | /// @return l1Address the address in the L1 that triggered the tx to L2
38 | function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
39 | unchecked {
40 | l1Address = address(uint160(l2Address) - offset);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/bindings-go/Makefile:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | GO_CMD=go
4 | GO_BUILD=$(GO_CMD) build
5 | CMD_PATH=./gen/main.go
6 | DIST=dist
7 | BINARY_NAME=bindings
8 |
9 | PKG="bindings"
10 | MONOREPO_BASE="$(shell dirname $(realpath .))"
11 | CONTRACTS_DIR="${MONOREPO_BASE}/contracts"
12 | GETH_DIR := "${MONOREPO_BASE}/services/el_clients/go-ethereum"
13 |
14 | .PHONY: all # Run all necessary steps and generate bindings
15 | all: bindings
16 |
17 | .PHONY: abigen # Install latest abigen command binary
18 | abigen:
19 | cd "${GETH_DIR}" && \
20 | go run build/ci.go install ./cmd/abigen
21 |
22 | .PHONY: compile # Compile contracts from the monorepo
23 | compile:
24 | @echo "monorepo-base: ${MONOREPO_BASE}"
25 | @echo "contracts-dir: ${CONTRACTS_DIR}"
26 | @cd "${CONTRACTS_DIR}" && \
27 | pnpm build
28 |
29 | .PHONY: build # Build binary
30 | build:
31 | mkdir -p $(DIST)
32 | CGO_ENABLED=1 $(GO_BUILD) -o ./dist/$(BINARY_NAME) -v $(CMD_PATH)
33 |
34 | .PHONY: test # Run tests
35 | test:
36 | go test $(go list ./... | grep -v /bindings/bindings)
37 |
38 | .PHONY: bindings # Run bindings generation alone
39 | bindings: compile abigen
40 | @go run ./gen/main.go \
41 | -out ./bindings \
42 | -contracts ./artifacts.json \
43 | -package "${PKG}" \
44 | -contract-dir "${CONTRACTS_DIR}" \
45 | -abigen-bin "${GETH_DIR}/build/bin/abigen"
46 |
47 | .PHONY: lint-test # Run lint-tests
48 | lint-test:
49 | go mod tidy
50 | @go install golang.org/x/tools/cmd/goimports@latest
51 | goimports -local github.com/SpecularL2/bindings -w .
52 | go fmt ./...
53 | @golangci-lint -v run ./...
54 | @test -z "$$(golangci-lint run ./...)"
55 |
56 | .PHONY: lint # Run lint-tests (alias)
57 | lint: lint-test
58 |
59 | .PHONY: help # Help - list of targets with descriptions
60 | help:
61 | @echo ''
62 | @echo 'Usage:'
63 | @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}'
64 | @echo ''
65 | @echo 'Targets:'
66 | @grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/ \1\t\2/' | expand -t20
67 |
--------------------------------------------------------------------------------
/config/example/.genesis.env:
--------------------------------------------------------------------------------
1 | # This dotenv contains the env variables for:
2 | # 1. genesis generation: via `sbin/create_genesis.sh`.
3 | # 2. contract deployment: via `sbin/start_l1.sh` and/or `sbin/deploy_l1_contracts.sh`.
4 | # This file is optional; it is only necessary for new deployments
5 | # (i.e. where the genesis isn't already generated and the contracts must be deployed).
6 |
7 | # Path to the genesis config.
8 | # Required for genesis generation.
9 | # This config is used to generate the genesis JSON at the configured $GENESIS_PATH.
10 | GENESIS_CFG_PATH=./genesis_config.json
11 | # Path to the actual (target) genesis.
12 | # File does not need to exist at configuration-time.
13 | # Required for genesis generation and contract deployment.
14 | GENESIS_PATH=./genesis.json
15 | # Path to the base rollup config.
16 | # Required for genesis generation.
17 | BASE_ROLLUP_CFG_PATH=./base_sp_rollup.json
18 | # Path to the actual (target) rollup config.
19 | # File does not need to exist at configuration-time (it will be generated).
20 | # Required for genesis generation and contract deployment.
21 | # Note: file name must start with "sp_" to support Magi.
22 | ROLLUP_CFG_PATH=./sp_rollup.json
23 | # Path to the exported genesis hash file
24 | # File does not need to exist at configuration-time (it will be generated).
25 | # Required for genesis generation.
26 | GENESIS_EXPORTED_HASH_PATH=./genesis_hash.json
27 | # L1 network parameters.
28 | # What network to deploy to (using hardhat).
29 | # Required for contract deployment.
30 | L1_NETWORK=localhost
31 | # What software stack to use to run the L1 network. Optional.
32 | # Required for contract deployment. Options: [geth|hardhat]
33 | # Note: if L1_STACK="geth", the L1 chain ID must be set to 1337 (i.e. in the base rollup cfg)
34 | L1_STACK=geth
35 | # Sets the L1 block speed (in terms of blocks-per-second)
36 | L1_PERIOD=2
37 | # L1 endpoint.
38 | # Required for contract deployment.
39 | # Note: if L1_STACK="hardhat", use host 127.0.0.1 (local) or 0.0.0.0 (docker)
40 | L1_ENDPOINT=ws://172.17.0.1:8545
41 |
--------------------------------------------------------------------------------
/contracts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@specularl2/contracts",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "files": [
7 | "src/**/*.sol",
8 | "artifacts/src/**/*.json",
9 | "!artifacts/src/**/*.dbg.json"
10 | ],
11 | "scripts": {
12 | "build": "hardhat compile",
13 | "lint:fix": "forge fmt && npx prettier scripts/ -w",
14 | "lint:check": "forge fmt --check && npx prettier scripts/ -c",
15 | "lint:forge-tests:check": "npx ts-node scripts/forge_test_names.ts",
16 | "test": "forge test",
17 | "preinstall": "npx only-allow pnpm",
18 | "prepublishOnly": "pnpm build"
19 | },
20 | "keywords": [],
21 | "author": "Specular contributors",
22 | "license": "Apache-2.0",
23 | "devDependencies": {
24 | "@ethersproject/abi": "^5.7.0",
25 | "@ethersproject/bytes": "^5.7.0",
26 | "@ethersproject/providers": "^5.7.2",
27 | "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13",
28 | "@nomiclabs/hardhat-etherscan": "^3.1.0",
29 | "@nomiclabs/hardhat-waffle": "^2.0.3",
30 | "@typechain/ethers-v5": "^10.2.0",
31 | "@typechain/hardhat": "^6.1.5",
32 | "@types/inquirer": "^9.0.3",
33 | "@types/node": "^18.13.0",
34 | "@typescript-eslint/eslint-plugin": "^5.52.0",
35 | "@typescript-eslint/parser": "^5.52.0",
36 | "chai": "^4.3.6",
37 | "dotenv": "^16.0.1",
38 | "eslint": "^8.34.0",
39 | "ethereum-waffle": "^3.4.4",
40 | "ethers": "^5.7.2",
41 | "hardhat": "^2.12.7",
42 | "hardhat-abi-exporter": "^2.10.0",
43 | "hardhat-deploy": "^0.11.18",
44 | "inquirer": "^8.2.5",
45 | "prettier": "^3.1.1",
46 | "ts-node": "^10.9.1",
47 | "typechain": "^8.1.1",
48 | "typescript": "^4.9.3"
49 | },
50 | "dependencies": {
51 | "@openzeppelin/contracts": "^4.9.3",
52 | "@openzeppelin/contracts-upgradeable": "^4.9.3",
53 | "@openzeppelin/hardhat-upgrades": "^1.22.1",
54 | "commander": "^11.1.0",
55 | "ethereumjs-util": "^7.1.5",
56 | "glob": "^10.2.3",
57 | "lint-staged": "^13.1.3"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/contracts/test/utils/Utils.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Modifications Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity >=0.8.0;
20 |
21 | import {DSTest} from "ds-test/test.sol";
22 | import {Vm} from "forge-std/Vm.sol";
23 |
24 | // common utilities for forge tests
25 | contract Utils is DSTest {
26 | Vm internal immutable vm = Vm(HEVM_ADDRESS);
27 | bytes32 internal nextUser = keccak256(abi.encodePacked("user address"));
28 |
29 | function getNextUserAddress() external returns (address payable) {
30 | // bytes32 to address conversion
31 | address payable user = payable(address(uint160(uint256(nextUser))));
32 | nextUser = keccak256(abi.encodePacked(nextUser));
33 | return user;
34 | }
35 |
36 | // create users with 100 ether balance
37 | function createUsers(uint256 userNum) external returns (address payable[] memory) {
38 | address payable[] memory users = new address payable[](userNum);
39 | for (uint256 i = 0; i < userNum; i++) {
40 | address payable user = this.getNextUserAddress();
41 | vm.deal(user, 100 ether);
42 | users[i] = user;
43 | }
44 | return users;
45 | }
46 |
47 | // move block.number forward by a given number of blocks
48 | function mineBlocks(uint256 numBlocks) external {
49 | uint256 targetBlock = block.number + numBlocks;
50 | vm.roll(targetBlock);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/charts/specular/templates/sidecar-pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | creationTimestamp: null
5 | labels:
6 | io.specular.network/sp-network: "true"
7 | io.specular.service: sidecar
8 | name: sidecar
9 | spec:
10 | initContainers:
11 | - name: wait-for-sp-geth
12 | image: busybox:latest
13 | command: ["sh", "-c", "until [ -f /tmp/.start_sp_magi.sh.lock ]; do sleep 2; done"]
14 | volumeMounts:
15 | - mountPath: /tmp
16 | name: {{ .Values.volume.efs.name }}
17 | containers:
18 | - command: ["bash", "-c", "../sbin/entrypoint.sh start start_sidecar.sh"]
19 | image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
20 | name: sidecar
21 | resources:
22 | {{- .Values.default_resources | toYaml | nindent 10 }}
23 | volumeMounts:
24 | - mountPath: {{ .Values.volume.efs.mountPath }}
25 | name: {{ .Values.volume.efs.name }}
26 | {{- range $i, $value := .Values.volume.configVolumeMounts.paths }}
27 | - name: {{ $.Values.volume.configVolumeMounts.name }}
28 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value }}
29 | subPath: {{ $value }}
30 | {{- end }}
31 | {{- if not .Values.generator.deploy }}
32 | {{- range $i, $value := .Values.volume.secrets }}
33 | - name: secret-volume
34 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value.file }}
35 | subPath: {{ $value.file }}
36 | readOnly: true
37 | {{- end }}
38 | {{- end }}
39 | workingDir: {{ .Values.volume.efs.mountPath }}
40 | terminationGracePeriodSeconds: 60
41 | restartPolicy: OnFailure
42 | volumes:
43 | - name: {{ .Values.volume.efs.name }}
44 | persistentVolumeClaim:
45 | claimName: {{ .Values.volume.efs.name }}
46 | - name: {{ .Values.volume.configVolumeMounts.name }}
47 | configMap:
48 | name: {{ .Values.volume.configVolumeMounts.name }}
49 | {{- if not .Values.generator.deploy }}
50 | - name: secret-volume
51 | secret:
52 | secretName: l2-secrets
53 | {{- end }}
54 | status: {}
55 |
--------------------------------------------------------------------------------
/bindings-go/bindings/ERC1967Proxy_more.go:
--------------------------------------------------------------------------------
1 | // Code generated - DO NOT EDIT.
2 | // This file is a generated binding and any manual changes will be lost.
3 |
4 | package bindings
5 |
6 | import (
7 | "encoding/json"
8 |
9 | "github.com/specularL2/specular/bindings-go/solc"
10 | )
11 |
12 | const ERC1967ProxyStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
13 |
14 | var ERC1967ProxyStorageLayout = new(solc.StorageLayout)
15 |
16 | var ERC1967ProxyDeployedBin = "0x60806040523661001357610011610017565b005b6100115b610027610022610067565b61009f565b565b606061004e838360405180606001604052806027815260200161025f602791396100c3565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b3660008037600080366000845af43d6000803e8080156100be573d6000f35b3d6000fd5b6060600080856001600160a01b0316856040516100e0919061020f565b600060405180830381855af49150503d806000811461011b576040519150601f19603f3d011682016040523d82523d6000602084013e610120565b606091505b50915091506101318683838761013b565b9695505050505050565b606083156101af5782516000036101a8576001600160a01b0385163b6101a85760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b50816101b9565b6101b983836101c1565b949350505050565b8151156101d15781518083602001fd5b8060405162461bcd60e51b815260040161019f919061022b565b60005b838110156102065781810151838201526020016101ee565b50506000910152565b600082516102218184602087016101eb565b9190910192915050565b602081526000825180602084015261024a8160408501602087016101eb565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220df5e088a16650cdc3010db20ecd1e2b26729605480c947e2f8d77aac24e8ff8464736f6c63430008110033"
17 | func init() {
18 | if err := json.Unmarshal([]byte(ERC1967ProxyStorageLayoutJSON), ERC1967ProxyStorageLayout); err != nil {
19 | panic(err)
20 | }
21 |
22 | layouts["ERC1967Proxy"] = ERC1967ProxyStorageLayout
23 | deployedBytecodes["ERC1967Proxy"] = ERC1967ProxyDeployedBin
24 | }
25 |
--------------------------------------------------------------------------------
/services/sidecar/rollup/rpc/eth/utils.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/avast/retry-go/v4"
8 | "github.com/ethereum/go-ethereum/core/types"
9 | "github.com/ethereum/go-ethereum/event"
10 |
11 | "github.com/specularL2/specular/services/sidecar/utils/fmt"
12 | "github.com/specularL2/specular/services/sidecar/utils/log"
13 | )
14 |
15 | type ethClient interface {
16 | HeaderByTag(ctx context.Context, tag BlockTag) (*types.Header, error)
17 | }
18 |
19 | func SubscribeNewHeadByPolling(
20 | ctx context.Context,
21 | client ethClient,
22 | headCh chan<- *types.Header,
23 | tag BlockTag,
24 | interval time.Duration,
25 | requestTimeout time.Duration,
26 | ) event.Subscription {
27 | return event.NewSubscription(func(unsub <-chan struct{}) error {
28 | ticker := time.NewTicker(interval)
29 | defer ticker.Stop()
30 | poll := func() error {
31 | reqCtx, cancel := context.WithTimeout(ctx, requestTimeout)
32 | header, err := client.HeaderByTag(reqCtx, tag)
33 | cancel()
34 | if err != nil {
35 | log.Warn("Failed to poll for latest L1 block header", "err", err)
36 | return err
37 | }
38 | headCh <- header
39 | return nil
40 | }
41 | if err := poll(); err != nil {
42 | return err
43 | }
44 | for {
45 | select {
46 | case <-ticker.C:
47 | if err := poll(); err != nil {
48 | return err
49 | }
50 | case <-ctx.Done():
51 | return ctx.Err()
52 | case <-unsub:
53 | return nil
54 | }
55 | }
56 | })
57 | }
58 |
59 | type LazyEthClient struct {
60 | *EthClient
61 | endpoint string
62 | retryOpts []retry.Option
63 | }
64 |
65 | func NewLazilyDialedEthClient(endpoint string, retryOpts ...retry.Option) *LazyEthClient {
66 | return &LazyEthClient{endpoint: endpoint, retryOpts: retryOpts}
67 | }
68 |
69 | func (c *LazyEthClient) EnsureDialed(ctx context.Context) error {
70 | if c.EthClient != nil {
71 | return nil
72 | }
73 | client, err := DialWithRetry(ctx, c.endpoint, c.retryOpts...)
74 | if err != nil {
75 | return fmt.Errorf("failed to connect to node: %w", err)
76 | }
77 | c.EthClient = client
78 | return nil
79 | }
80 |
--------------------------------------------------------------------------------
/contracts/scripts/bridge/deployERC20Token.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 | import { env } from "process";
3 | import * as dotenv from "dotenv";
4 | import { program } from "commander";
5 |
6 | type TokenInfo = {
7 | l1Address: String;
8 | name: String;
9 | symbol: String;
10 | };
11 |
12 | const address = "0x2A000000000000000000000000000000000000F0";
13 | const abi = [
14 | "function createMintableERC20(address _remoteToken, string memory _name, string memory _symbol)",
15 | "event MintableERC20Created(address indexed localToken, address indexed remoteToken, address deployer)",
16 | ];
17 |
18 | export async function deployERC20Token(
19 | token: TokenInfo,
20 | signer: ethers.Signer,
21 | ): Promise {
22 | const mintableFactory = new ethers.Contract(address, abi, signer);
23 |
24 | console.log({ mintableFactory });
25 | console.log({ token });
26 |
27 | const tx = await mintableFactory.createMintableERC20(
28 | token.l1Address,
29 | token.name,
30 | token.symbol,
31 | );
32 | console.log({ tx });
33 |
34 | const txWithLogs = await tx.wait();
35 | const deployEvent = mintableFactory.interface.parseLog(txWithLogs.logs[0]);
36 |
37 | return deployEvent.args.localToken;
38 | }
39 |
40 | program
41 | .requiredOption("--rpc ")
42 | .requiredOption("--name ")
43 | .requiredOption("--symbol ")
44 | .requiredOption("--address ");
45 |
46 | async function main() {
47 | dotenv.config();
48 | program.parse(process.argv);
49 | const options = program.opts();
50 | if (!env.DEPLOYER_PRIVATE_KEY) throw "please provide deployer private key";
51 |
52 | const key = new ethers.utils.SigningKey(env.DEPLOYER_PRIVATE_KEY);
53 | const provider = new ethers.providers.JsonRpcProvider(options.rpc);
54 | const signer = new ethers.Wallet(key, provider);
55 | const token = {
56 | l1Address: options.address,
57 | name: options.name,
58 | symbol: options.symbol,
59 | };
60 |
61 | const l2address = await deployERC20Token(token, signer);
62 | console.log("token deployed on L2 at ", l2address);
63 | }
64 |
65 | main().catch((error) => {
66 | console.error(error);
67 | process.exitCode = 1;
68 | });
69 |
--------------------------------------------------------------------------------
/services/sidecar/rollup/services/validator/interface.go:
--------------------------------------------------------------------------------
1 | package validator
2 |
3 | import (
4 | "context"
5 | "math/big"
6 | "time"
7 |
8 | "github.com/ethereum/go-ethereum/common"
9 | ethTypes "github.com/ethereum/go-ethereum/core/types"
10 |
11 | "github.com/specularL2/specular/bindings-go/bindings"
12 | "github.com/specularL2/specular/services/sidecar/rollup/rpc/bridge"
13 | "github.com/specularL2/specular/services/sidecar/rollup/rpc/eth"
14 | "github.com/specularL2/specular/services/sidecar/rollup/types"
15 | )
16 |
17 | type Config interface {
18 | GetAccountAddr() common.Address
19 | GetValidationInterval() time.Duration
20 | }
21 |
22 | type TxManager interface {
23 | Stake(ctx context.Context, stakeAmount *big.Int) (*ethTypes.Receipt, error)
24 | CreateAssertion(
25 | ctx context.Context,
26 | stateCommitment common.Hash,
27 | blockNum *big.Int,
28 | l1BlockHash common.Hash,
29 | l1BlockNum *big.Int,
30 | ) (*ethTypes.Receipt, error)
31 | ConfirmFirstUnresolvedAssertion(ctx context.Context) (*ethTypes.Receipt, error)
32 | RejectFirstUnresolvedAssertion(context.Context, common.Address) (*ethTypes.Receipt, error)
33 | RemoveStake(context.Context, common.Address) (*ethTypes.Receipt, error)
34 | }
35 |
36 | type BridgeClient interface {
37 | GetRequiredStakeAmount(context.Context) (*big.Int, error)
38 | GetStaker(context.Context, common.Address) (bindings.IRollupStaker, error)
39 | GetAssertion(context.Context, *big.Int) (bindings.IRollupAssertion, error)
40 | RequireFirstUnresolvedAssertionIsConfirmable(context.Context) (*bridge.UnsatisfiedCondition, error)
41 | RequireFirstUnresolvedAssertionIsRejectable(context.Context, common.Address) (*bridge.UnsatisfiedCondition, error)
42 | }
43 |
44 | type EthState interface {
45 | Head() types.BlockID
46 | Safe() types.BlockID
47 | Finalized() types.BlockID
48 | }
49 |
50 | type L2Client interface {
51 | EnsureDialed(ctx context.Context) error
52 | BlockNumber(ctx context.Context) (uint64, error)
53 | BlockByNumber(ctx context.Context, number *big.Int) (*ethTypes.Block, error)
54 | HeaderByTag(ctx context.Context, tag eth.BlockTag) (*ethTypes.Header, error)
55 | }
56 |
57 | type ErrGroup interface{ Go(f func() error) }
58 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | check_for_changes:
11 | runs-on: ubuntu-latest
12 | outputs:
13 | should_skip: ${{ steps.skip_check.outputs.should_skip }}
14 | steps:
15 | - id: skip_check
16 | uses: fkirc/skip-duplicate-actions@v5
17 | with:
18 | concurrent_skipping: same_content_newer
19 |
20 | lint_solidity:
21 | needs: check_for_changes
22 | if: needs.pre_job.outputs.should_skip != 'true'
23 | runs-on: ubuntu-latest
24 | defaults:
25 | run:
26 | working-directory: ./contracts
27 |
28 | steps:
29 | - uses: actions/checkout@v3
30 |
31 | - name: Cache Foundry toolchain
32 | uses: actions/cache@v2
33 | with:
34 | path: ~/.foundry
35 | key: ${{ runner.os }}-foundry-${{ hashFiles('**/foundry.toml') }}
36 | restore-keys: |
37 | ${{ runner.os }}-foundry-
38 |
39 | - name: Install Foundry
40 | uses: foundry-rs/foundry-toolchain@v1
41 | with:
42 | version: nightly
43 |
44 | - name: Run forge install
45 | run: forge install
46 |
47 | - name: Check if forge fmt was run
48 | run: forge fmt --check
49 |
50 | lint_bash:
51 | needs: check_for_changes
52 | if: needs.pre_job.outputs.should_skip != 'true'
53 | runs-on: ubuntu-latest
54 | defaults:
55 | run:
56 | working-directory: ./sbin
57 |
58 | steps:
59 | - uses: actions/checkout@v3
60 |
61 | - name: Install shfmt
62 | run: sudo apt-get install -y shfmt
63 |
64 | - name: Run shfmt
65 | run: shfmt -d .
66 |
67 | lint_ts_scripts:
68 | needs: check_for_changes
69 | if: needs.pre_job.outputs.should_skip != 'true'
70 | runs-on: ubuntu-latest
71 | defaults:
72 | run:
73 | working-directory: ./contracts
74 |
75 | steps:
76 | - uses: actions/checkout@v3
77 |
78 | - name: Install Node.js
79 | uses: actions/setup-node@v3
80 | with:
81 | node-version: 16
82 |
83 | - name: Run prettier
84 | run: npx prettier scripts/ -c
85 |
--------------------------------------------------------------------------------
/contracts/src/ISequencerInbox.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Modifications Copyright 2022, Specular contributors
5 | *
6 | * This file was changed in accordance to Apache License, Version 2.0.
7 | *
8 | * Copyright 2021, Offchain Labs, Inc.
9 | *
10 | * Licensed under the Apache License, Version 2.0 (the "License");
11 | * you may not use this file except in compliance with the License.
12 | * You may obtain a copy of the License at
13 | *
14 | * http://www.apache.org/licenses/LICENSE-2.0
15 | *
16 | * Unless required by applicable law or agreed to in writing, software
17 | * distributed under the License is distributed on an "AS IS" BASIS,
18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | * See the License for the specific language governing permissions and
20 | * limitations under the License.
21 | */
22 |
23 | pragma solidity ^0.8.0;
24 |
25 | import "./IDAProvider.sol";
26 |
27 | /**
28 | * @notice On-chain DA provider.
29 | */
30 | interface ISequencerInbox is IDAProvider {
31 | event TxBatchAppended();
32 |
33 | /// @dev Thrown when the given tx inclusion proof couldn't be verified.
34 | error ProofVerificationFailed();
35 |
36 | /// @dev Thrown when underflow occurs reading txBatchData
37 | error TxBatchDataUnderflow();
38 |
39 | /// @dev Thrown when a transaction batch has an incorrect version
40 | error TxBatchVersionIncorrect();
41 |
42 | /**
43 | * @notice Appends a batch of transactions (stored in calldata) and emits a TxBatchAppended event.
44 | * @param txBatchData Batch of RLP-encoded transactions, encoded as:
45 | * txBatchData format:
46 | * txBatchData = version || batchData (|| is concatenation)
47 | * where:
48 | * - version: uint8
49 | * - data: bytes
50 | * batchData format:
51 | * batchData = RLP([firstL2BlockNum, batchList])
52 | * where:
53 | * - firstL2BlockNum: uint256
54 | * - batchList: List[blockData]
55 | * blockData = [timestamp, txList]
56 | * where:
57 | * - timestamp: uint256
58 | * - txList: bytes
59 | */
60 | function appendTxBatch(bytes calldata txBatchData) external;
61 | }
62 |
--------------------------------------------------------------------------------
/services/sidecar/proof/prover/state_generator.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022, Specular contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package prover
16 |
17 | import (
18 | "math/big"
19 |
20 | "github.com/ethereum/go-ethereum/common"
21 | "github.com/ethereum/go-ethereum/core/vm"
22 | )
23 |
24 | type GeneratedState struct {
25 | VMHash common.Hash
26 | Gas uint64
27 | }
28 |
29 | type StateGenerator struct {
30 | // Global
31 | states []GeneratedState
32 | }
33 |
34 | func NewStateGenerator() *StateGenerator {
35 | return &StateGenerator{}
36 | }
37 |
38 | func (l *StateGenerator) CaptureTxStart(gasLimit uint64) {}
39 |
40 | func (l *StateGenerator) CaptureTxEnd(restGas uint64) {}
41 |
42 | func (l *StateGenerator) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
43 | }
44 |
45 | func (l *StateGenerator) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
46 | l.states = append(l.states, GeneratedState{common.Hash{}, gas})
47 | }
48 |
49 | func (l *StateGenerator) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
50 | }
51 |
52 | func (l *StateGenerator) CaptureExit(output []byte, gasUsed uint64, err error) {
53 | }
54 |
55 | func (l *StateGenerator) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
56 | }
57 |
58 | func (l *StateGenerator) CaptureEnd(output []byte, gasUsed uint64, err error) {
59 | }
60 |
61 | func (l *StateGenerator) GetGeneratedStates() ([]GeneratedState, error) {
62 | return l.states, nil
63 | }
64 |
--------------------------------------------------------------------------------
/docker/specular.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM 792926601177.dkr.ecr.us-east-2.amazonaws.com/specular-platform:e2e-latest as build
2 |
3 | FROM golang:bullseye
4 |
5 | ENV NODE_MAJOR=16
6 | ENV FOUNDRY_VERSION="nightly"
7 | ENV FOUNDRY_TAR="foundry_nightly_linux_amd64.tar.gz"
8 |
9 | WORKDIR /tmp
10 |
11 | RUN apt install -y python3 ca-certificates curl gnupg
12 | RUN mkdir -p /etc/apt/keyrings && \
13 | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
14 | echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
15 | apt-get update && \
16 | apt-get install nodejs -y
17 |
18 | RUN wget https://github.com/foundry-rs/foundry/releases/download/$FOUNDRY_VERSION/$FOUNDRY_TAR && \
19 | tar xzvf $FOUNDRY_TAR && \
20 | mv cast /usr/local/bin
21 |
22 | # copy everything we need
23 |
24 | RUN mkdir -p /specular/workspace
25 | RUN mkdir -p /specular/sbin
26 | RUN mkdir -p /specular/contracts
27 |
28 | # install hardhar
29 | COPY --from=build /specular/package.json /specular
30 | COPY --from=build /specular/contracts /specular/contracts
31 | COPY --from=build /specular/pnpm-lock.yaml /specular/pnpm-lock.yaml
32 | COPY --from=build /specular/pnpm-workspace.yaml /specular/pnpm-workspace.yaml
33 |
34 | WORKDIR /specular
35 |
36 | RUN npm install -g pnpm
37 | RUN pnpm install
38 |
39 |
40 | COPY --from=build /specular/config/local_docker /specular/workspace
41 | COPY --from=build /specular/sbin /specular/sbin
42 |
43 | # DEBUG/LOCAL BUILD
44 | # COPY ../config/local_docker /specular/workspace
45 | # COPY ../sbin /specular/sbin
46 | # COPY ../services /specular/services
47 |
48 | WORKDIR /specular/workspace
49 |
50 | COPY --from=build /specular/ops/ /specular/ops/
51 | COPY --from=build /specular/services/sidecar/build/bin/sidecar /usr/local/bin/sidecar
52 | COPY --from=build /specular/ops/build/bin/genesis /usr/local/bin/genesis
53 | COPY --from=build /specular/services/cl_clients/magi/target/debug/magi /usr/local/bin/magi
54 | COPY --from=build /specular/services/el_clients/go-ethereum/build/bin/geth /usr/local/bin/geth
55 |
56 |
57 | EXPOSE 4011 4012 4013 8545
58 |
59 |
--------------------------------------------------------------------------------
/packages/sdk/src/interfaces/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Provider,
3 | TransactionReceipt,
4 | TransactionResponse,
5 | } from "@ethersproject/abstract-provider";
6 | import { Signer } from "@ethersproject/abstract-signer";
7 | import { Contract, BigNumber } from "ethers";
8 |
9 | /**
10 | * L1 network chain IDs.
11 | */
12 | export enum L1ChainID {
13 | MAINNET = 1,
14 | SEPOLIA = 11155111,
15 | HARDHAT_LOCAL = 31337,
16 | }
17 |
18 | /**
19 | * Message finalizaton status.
20 | */
21 | export enum MessageStatus {
22 | // not ready to be finalized
23 | PENDING = 0,
24 | // ready to be finalized
25 | READY = 1,
26 | // already finalized
27 | DONE = 2,
28 | }
29 |
30 | /**
31 | * L2 network chain IDs.
32 | */
33 | export enum L2ChainID {
34 | SPECULAR = 93481,
35 | SPECULAR_HARDHAT_LOCAL = 13527,
36 | }
37 |
38 | /**
39 | * Stuff that can be coerced into a transaction.
40 | */
41 | export type TransactionLike = string | TransactionReceipt | TransactionResponse;
42 |
43 | /**
44 | * Stuff that can be coerced into a provider.
45 | */
46 | export type ProviderLike = string | Provider;
47 |
48 | /**
49 | * Stuff that can be coerced into a signer.
50 | */
51 | export type SignerLike = string | Signer;
52 |
53 | /**
54 | * Stuff that can be coerced into a signer or provider.
55 | */
56 | export type SignerOrProviderLike = SignerLike | ProviderLike;
57 |
58 | /**
59 | * Stuff that can be coerced into an address.
60 | */
61 | export type AddressLike = string | Contract;
62 |
63 | /**
64 | * Stuff that can be coerced into a number.
65 | */
66 | export type NumberLike = string | number | BigNumber;
67 |
68 | /**
69 | * L1 contract references.
70 | */
71 | export interface L1Contracts {
72 | L1Portal: AddressLike;
73 | L1StandardBridge: AddressLike;
74 | L1Rollup: AddressLike;
75 | }
76 |
77 | /**
78 | * L2 contract references.
79 | */
80 | export interface L2Contracts {
81 | UUPSPlaceholder: AddressLike;
82 | L1Oracle: AddressLike;
83 | L2Portal: AddressLike;
84 | L2StandardBridge: AddressLike;
85 | L1FeeVault: AddressLike;
86 | L2BaseFeeVault: AddressLike;
87 | }
88 |
89 | /**
90 | * L1 and L2 contracts references.
91 | */
92 | export interface ContractsLike {
93 | l1: L1Contracts;
94 | l2: L2Contracts;
95 | }
96 |
--------------------------------------------------------------------------------
/packages/onboarding/src/onboarding/service.ts:
--------------------------------------------------------------------------------
1 | import { ethers, BigNumber } from "ethers";
2 | import { JsonRpcProvider } from "@ethersproject/providers";
3 |
4 | import {
5 | OnboardingServiceConfig as OnboardingConfig,
6 | CrossDomainMessenger,
7 | CrossDomainMessage,
8 | MessageProof,
9 | getStorageKey,
10 | } from ".";
11 |
12 | export class OnboardingService {
13 | readonly config: OnboardingConfig;
14 |
15 | readonly l1Provider: JsonRpcProvider;
16 | readonly l2Provider: JsonRpcProvider;
17 |
18 | messenger: CrossDomainMessenger | undefined = undefined;
19 |
20 | readonly l2Funder: ethers.Wallet;
21 |
22 | constructor(config: OnboardingConfig) {
23 | this.config = config;
24 |
25 | this.l1Provider = new ethers.providers.JsonRpcProvider(
26 | config.l1ProviderEndpoint
27 | );
28 | this.l2Provider = new ethers.providers.JsonRpcProvider(
29 | config.l2ProviderEndpoint
30 | );
31 |
32 | this.l2Funder = new ethers.Wallet(
33 | config.l2FunderPrivateKey,
34 | this.l2Provider
35 | );
36 | }
37 |
38 | async start() {
39 | this.messenger = await CrossDomainMessenger.create({
40 | l2Funder: this.l2Funder,
41 | });
42 | console.log("started onboarding service");
43 | }
44 |
45 | async generateDepositProof(
46 | depositHash: string,
47 | blockNumber: BigNumber
48 | ): Promise {
49 | const rawProof = await this.l1Provider.send("eth_getProof", [
50 | this.config.l1PortalAddress,
51 | [getStorageKey(depositHash)],
52 | ethers.utils.hexValue(blockNumber),
53 | ]);
54 | return {
55 | accountProof: rawProof.accountProof,
56 | storageProof: rawProof.storageProof[0].proof,
57 | };
58 | }
59 |
60 | async fundDeposit(depositTx: CrossDomainMessage, depositHash: string) {
61 | if (this.messenger === undefined) {
62 | throw new Error("Messenger not started");
63 | }
64 | const blockNumber = await this.messenger.getL1OracleBlockNumber();
65 | const proof = await this.generateDepositProof(depositHash, blockNumber);
66 | console.log(proof);
67 | const tx = await this.messenger.finalizeDeposit(
68 | blockNumber,
69 | depositTx,
70 | proof
71 | );
72 | return tx.transactionHash;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/sbin/create_genesis.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | # Currently the local sbin paths are relative to the project root.
4 | SBIN=$(dirname "$(readlink -f "$0")")
5 | SBIN="$(
6 | cd "$SBIN"
7 | pwd
8 | )"
9 | . $SBIN/utils/utils.sh
10 | ROOT_DIR=$SBIN/..
11 |
12 | # Check that the all required dotenv files exists.
13 | reqdotenv "paths" ".paths.env"
14 | reqdotenv "genesis" ".genesis.env"
15 | reqdotenv "contracts" ".contracts.env"
16 | reqdotenv "deployments" ".deployments.env"
17 |
18 | echo "Using $OPS_DIR as ops directory."
19 | # Get relative paths for $OPS_DIR
20 | GENESIS_CFG_PATH=$(relpath $GENESIS_CFG_PATH $OPS_DIR)
21 | GENESIS_PATH=$(relpath $GENESIS_PATH $OPS_DIR)
22 | GENESIS_EXPORTED_HASH_PATH=$(relpath $GENESIS_EXPORTED_HASH_PATH $OPS_DIR)
23 | echo "Generating new genesis file at $GENESIS_PATH and exporting hash to $GENESIS_EXPORTED_HASH_PATH"
24 | cd $OPS_DIR
25 | guard_overwrite $GENESIS_PATH $AUTO_ACCEPT
26 | # Create genesis.json file.
27 | FLAGS=(
28 | "--genesis-config $GENESIS_CFG_PATH"
29 | "--out $GENESIS_PATH"
30 | "--l1-block $SEQUENCER_INBOX_DEPLOYED_BLOCK"
31 | "--l1-rpc-url $L1_ENDPOINT"
32 | "--export-hash $GENESIS_EXPORTED_HASH_PATH"
33 | "--l1-portal-address $L1PORTAL_ADDR"
34 | "--l1-standard-bridge-address $L1STANDARD_BRIDGE_ADDR"
35 | "--alloc $SEQUENCER_ADDRESS,$VALIDATOR_ADDRESS,$DEPLOYER_ADDRESS"
36 | )
37 |
38 | # hoop: I don't have the patience rn to determine why this isn't being sourced
39 | if [[ -z ${OPS_GENESIS_BIN+x} ]]; then
40 | CMD="/usr/local/bin/genesis ${FLAGS[@]}"
41 | else
42 | CMD="$OPS_GENESIS_BIN ${FLAGS[@]}"
43 | fi
44 |
45 | echo "Running $CMD"
46 | eval $CMD
47 |
48 | # Initialize a reference to the config files at
49 | # "contracts/.genesis" (using relative paths as appropriate).
50 | CONTRACTS_ENV=$CONTRACTS_DIR/.genesis.env
51 | guard_overwrite $CONTRACTS_ENV $AUTO_ACCEPT
52 | # Write file, using relative paths.
53 | echo "Initializing contracts genesis dotenv $CONTRACTS_ENV"
54 | GENESIS_PATH=$(relpath $GENESIS_PATH $CONTRACTS_DIR)
55 | GENESIS_EXPORTED_HASH_PATH=$(relpath $GENESIS_EXPORTED_HASH_PATH $CONTRACTS_DIR)
56 | BASE_ROLLUP_CFG_PATH=$(relpath $BASE_ROLLUP_CFG_PATH $CONTRACTS_DIR)
57 | echo GENESIS_PATH=$GENESIS_PATH >>$CONTRACTS_ENV
58 | echo GENESIS_EXPORTED_HASH_PATH=$GENESIS_EXPORTED_HASH_PATH >>$CONTRACTS_ENV
59 |
--------------------------------------------------------------------------------
/contracts/src/libraries/FeeVault.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
5 | import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
6 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
7 |
8 | /// @title FeeVault
9 | /// @notice The FeeVault contract contains the basic logic for the various different vault contracts
10 | /// used to hold fee revenue generated by the L2 system.
11 | abstract contract FeeVault is Initializable, UUPSUpgradeable, OwnableUpgradeable {
12 | /// @notice Minimum balance before a withdrawal can be triggered.
13 | uint256 public minWithdrawalAmount;
14 |
15 | /// @notice Wallet that will receive the fees.
16 | address public withdrawalAddress;
17 |
18 | /// @notice Total amount of wei processed by the contract.
19 | uint256 public totalProcessed;
20 |
21 | /// @notice Emitted each time a withdrawal occurs.
22 | /// @param value Amount that was withdrawn (in wei).
23 | /// @param to Address that the funds were sent to.
24 | /// @param from Address that triggered the withdrawal.
25 | event Withdrawal(uint256 value, address to, address from);
26 |
27 | /// @notice Allow the contract to receive ETH.
28 | receive() external payable {}
29 |
30 | /// @notice Triggers a withdrawal of funds to the fee wallet on L1 or L2.
31 | function withdraw() external {
32 | require(
33 | address(this).balance >= minWithdrawalAmount,
34 | "FeeVault: withdrawal amount must be greater than minimum withdrawal amount"
35 | );
36 |
37 | uint256 value = address(this).balance;
38 | totalProcessed += value;
39 |
40 | emit Withdrawal(value, withdrawalAddress, msg.sender);
41 |
42 | (bool success,) = withdrawalAddress.call{value: value}(hex"");
43 | require(success, "FeeVault: failed to send ETH to fee recipient");
44 | }
45 |
46 | function initialize() public initializer {
47 | __Ownable_init();
48 | __UUPSUpgradeable_init();
49 | }
50 |
51 | function _authorizeUpgrade(address) internal override onlyOwner {}
52 | }
53 |
--------------------------------------------------------------------------------
/contracts/src/challenge/verifier/IVerificationContext.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 |
3 | /*
4 | * Copyright 2022, Specular contributors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | pragma solidity ^0.8.0;
20 |
21 | interface IVerificationContext {
22 | // Block
23 | function getBlockHash(uint8 number) external view returns (bytes32);
24 | function getCoinbase() external view returns (address);
25 | function getTimestamp() external view returns (uint256);
26 | function getBlockNumber() external view returns (uint256);
27 | function getDifficulty() external view returns (uint256);
28 | function getGasLimit() external view returns (uint64);
29 | function getChainID() external view returns (uint256);
30 | function getBaseFee() external view returns (uint256);
31 |
32 | // Transaction
33 | enum TxnType {
34 | TRANSFER,
35 | CREATE,
36 | CALL
37 | }
38 |
39 | function getStateRoot() external view returns (bytes32);
40 | function getEndStateRoot() external view returns (bytes32);
41 | function getOrigin() external view returns (address);
42 | function getRecipient() external view returns (address);
43 | function getTxnType() external view returns (TxnType);
44 | function getValue() external view returns (uint256);
45 | function getGas() external view returns (uint256);
46 | function getGasPrice() external view returns (uint256);
47 | function getInput() external view returns (bytes memory);
48 | function getInputSize() external view returns (uint64);
49 | function getInputRoot() external view returns (bytes32);
50 | // Only called when txn type is CREATE
51 | function getCodeMerkleFromInput() external view returns (bytes32);
52 | }
53 |
--------------------------------------------------------------------------------
/packages/sdk/scripts/test_deposit.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 | import { formatEther } from "ethers/lib/utils";
3 | import { ServiceBridge } from "../src/service_bridge";
4 | import { delay } from "../src/utils";
5 | import { resolve } from "path";
6 | import dotenv from "dotenv";
7 | dotenv.config({ path: resolve(__dirname, "../.env.test") });
8 |
9 | async function main() {
10 | const l1Url: string = process.env.L1_URL!;
11 | const l2Url: string = process.env.L2_URL!;
12 | const l1ChainId: number = parseInt(process.env.L1_CHAIN_ID!);
13 | const l2ChainId: number = parseInt(process.env.L2_CHAIN_ID!);
14 |
15 | const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url);
16 | const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url);
17 | const l1Wallet = new ethers.Wallet(
18 | "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
19 | l1RpcProvider,
20 | );
21 | const l2Wallet = new ethers.Wallet(
22 | "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
23 | l2RpcProvider,
24 | );
25 |
26 | const l1balance = await l1Wallet.getBalance();
27 | console.log("L1 balancee: ", l1balance);
28 |
29 | const serviceBridge = new ServiceBridge({
30 | l1SignerOrProvider: l1Wallet, // l1 signer
31 | l2SignerOrProvider: l2Wallet, // l2 signer
32 | l1ChainId,
33 | l2ChainId,
34 | });
35 |
36 | const depositETHResponse = await serviceBridge.depositETH(1000);
37 |
38 | console.log("depositETHResponse: ", depositETHResponse);
39 |
40 | // 2 block confirmations
41 | const depositETHReceipt = await depositETHResponse.wait(2);
42 |
43 | console.log("depositETHReceipt: ", depositETHReceipt);
44 |
45 | let messageStatus = await serviceBridge.getDepositStatus(depositETHReceipt);
46 |
47 | while (messageStatus == 0) {
48 | await delay(5000);
49 | console.log("...Waiting for the TX to be ready for finalization...");
50 | messageStatus = await serviceBridge.getDepositStatus(depositETHReceipt);
51 | }
52 |
53 | const finalizeDepositResponse =
54 | await serviceBridge.finalizeDeposit(depositETHReceipt);
55 |
56 | const finalizeDepositReceipt = await finalizeDepositResponse.wait();
57 |
58 | console.log({ finalizeDepositReceipt });
59 | }
60 |
61 | main()
62 | .then(() => process.exit(0))
63 | .catch((error) => {
64 | console.error(error);
65 | process.exit(1);
66 | });
67 |
--------------------------------------------------------------------------------
/contracts/src/bridge/IL2Portal.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import "../libraries/Types.sol";
5 |
6 | interface IL2Portal {
7 | /**
8 | * @notice Emitted any time a withdrawal is initiated.
9 | *
10 | * @param nonce Unique value corresponding to each withdrawal.
11 | * @param sender The L2 account address which initiated the withdrawal.
12 | * @param target The L1 account address the call will be send to.
13 | * @param value The ETH value submitted for withdrawal, to be forwarded to the target.
14 | * @param gasLimit The minimum amount of gas that must be provided when withdrawing on L1.
15 | * @param data The data to be forwarded to the target on L1.
16 | * @param withdrawalHash The hash of the withdrawal.
17 | */
18 | event WithdrawalInitiated(
19 | uint256 indexed nonce,
20 | address indexed sender,
21 | address indexed target,
22 | uint256 value,
23 | uint256 gasLimit,
24 | bytes data,
25 | bytes32 withdrawalHash
26 | );
27 |
28 | /**
29 | * @notice Emitted when a deposit transaction is finalized.
30 | *
31 | * @param depositHash Hash of the deposit transaction.
32 | * @param success Whether the deposit transaction was successful.
33 | */
34 | event DepositFinalized(bytes32 indexed depositHash, bool success);
35 |
36 | /**
37 | * @notice Sends a message from L2 to L1.
38 | *
39 | * @param _target Address to call on L1 execution.
40 | * @param _gasLimit Minimum gas limit for executing the message on L1.
41 | * @param _data Data to forward to L1 target.
42 | */
43 | function initiateWithdrawal(address _target, uint256 _gasLimit, bytes memory _data) external payable;
44 |
45 | /**
46 | * @notice Finalizes a deposit transaction.
47 | *
48 | * @param depositTx Deposit transaction to finalize.
49 | * @param depositAccountProof Inclusion proof of the L1Portal contract's storage root.
50 | * @param depositProof Inclusion proof of the deposit in L1Portal contract.
51 | */
52 | function finalizeDepositTransaction(
53 | uint256 l1BlockNumber,
54 | Types.CrossDomainMessage memory depositTx,
55 | bytes[] calldata depositAccountProof,
56 | bytes[] calldata depositProof
57 | ) external;
58 | }
59 |
--------------------------------------------------------------------------------
/services/sidecar/proof/prover/one_step_prover.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022, Specular contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package prover
16 |
17 | import (
18 | "math/big"
19 |
20 | "github.com/ethereum/go-ethereum/common"
21 | "github.com/ethereum/go-ethereum/core/vm"
22 |
23 | "github.com/specularL2/specular/services/sidecar/proof/proof"
24 | )
25 |
26 | type PlaceHolderProof struct{}
27 |
28 | func (p *PlaceHolderProof) Encode() []byte {
29 | return []byte{'p', 'r', 'o', 'o', 'f'}
30 | }
31 |
32 | type OneStepProver struct {
33 | // Config
34 | target common.Hash
35 | step uint64
36 | }
37 |
38 | func NewProver(target common.Hash, step uint64) *OneStepProver {
39 | return &OneStepProver{target: target, step: step}
40 | }
41 |
42 | func (l *OneStepProver) CaptureTxStart(gasLimit uint64) {}
43 |
44 | func (l *OneStepProver) CaptureTxEnd(restGas uint64) {}
45 |
46 | func (l *OneStepProver) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
47 | }
48 |
49 | func (l *OneStepProver) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
50 | }
51 |
52 | func (l *OneStepProver) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
53 | }
54 |
55 | func (l *OneStepProver) CaptureExit(output []byte, gasUsed uint64, err error) {
56 | }
57 |
58 | func (l *OneStepProver) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
59 | }
60 |
61 | func (l *OneStepProver) CaptureEnd(output []byte, gasUsed uint64, err error) {
62 | }
63 |
64 | func (l *OneStepProver) GetProof() (*proof.OneStepProof, error) {
65 | proof := proof.EmptyProof()
66 | proof.AddProof(&PlaceHolderProof{})
67 | return proof, nil
68 | }
69 |
--------------------------------------------------------------------------------
/charts/specular/templates/l1-geth-pod.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.enabled.l1Geth -}}
2 | apiVersion: v1
3 | kind: Pod
4 | metadata:
5 | creationTimestamp: null
6 | labels:
7 | io.specular.network/sp-network: "true"
8 | io.specular.service: l1-geth
9 | name: l1-geth
10 | spec:
11 | initContainers:
12 | - name: wait-for-sp-generator
13 | image: busybox:latest
14 | command: ["sh", "-c", "until [ -f /tmp/.generate_secrets.sh.lock ]; do sleep 2; done"]
15 | volumeMounts:
16 | - mountPath: /tmp
17 | name: {{ .Values.volume.efs.name }}
18 | containers:
19 | - name: l1-geth
20 | command: ["bash", "-c", "../sbin/entrypoint.sh start start_l1.sh -c -d -w -y"]
21 | image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
22 | livenessProbe:
23 | exec:
24 | command: ["sh", "-c", "test -e /specular/workspace/.start_l1.sh.lock"]
25 | failureThreshold: 300
26 | initialDelaySeconds: 60
27 | periodSeconds: 1
28 | lifecycle:
29 | preStop:
30 | exec:
31 | command: ["sh", "-c", "rm -f /specular/workspace/.*.lock"]
32 | ports:
33 | - containerPort: 8545
34 | hostPort: 8545
35 | protocol: TCP
36 | resources:
37 | {{- .Values.default_resources | toYaml | nindent 10 }}
38 | tty: true
39 | volumeMounts:
40 | - mountPath: {{ .Values.volume.efs.mountPath }}
41 | name: {{ .Values.volume.efs.name }}
42 | {{- range $i, $value := .Values.volume.configVolumeMounts.paths }}
43 | - name: {{ $.Values.volume.configVolumeMounts.name }}
44 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value }}
45 | subPath: {{ $value }}
46 | {{- end }}
47 | {{- if not .Values.generator.deploy }}
48 | {{- range $i, $value := .Values.volume.secrets }}
49 | - name: secret-volume
50 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value.file }}
51 | subPath: {{ $value.file }}
52 | readOnly: true
53 | {{- end }}
54 | {{- end }}
55 | workingDir: {{ .Values.volume.efs.mountPath }}
56 | restartPolicy: Always
57 | volumes:
58 | - name: {{ .Values.volume.efs.name }}
59 | persistentVolumeClaim:
60 | claimName: {{ .Values.volume.efs.name }}
61 | - name: {{ .Values.volume.configVolumeMounts.name }}
62 | configMap:
63 | name: {{ .Values.volume.configVolumeMounts.name }}
64 | status: {}
65 | {{- end -}}
66 |
--------------------------------------------------------------------------------
/charts/specular/templates/sp-magi-pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | creationTimestamp: null
5 | labels:
6 | io.specular.network/sp-network: "true"
7 | io.specular.service: sp-magi
8 | name: sp-magi
9 | spec:
10 | initContainers:
11 | - name: wait-for-sp-geth
12 | image: busybox:latest
13 | command: ["sh", "-c", "until [ -f /tmp/.start_sp_geth.sh.lock ]; do sleep 2; done"]
14 | volumeMounts:
15 | - mountPath: /tmp
16 | name: {{ .Values.volume.efs.name }}
17 | containers:
18 | - name: sp-magi
19 | command: ["bash", "-c", "../sbin/entrypoint.sh start start_sp_magi.sh"]
20 | image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
21 | livenessProbe:
22 | exec:
23 | command: ["sh", "-c", "test -e /specular/workspace/.start_sp_magi.sh.lock"]
24 | failureThreshold: 300
25 | initialDelaySeconds: 20
26 | periodSeconds: 10
27 | lifecycle:
28 | preStop:
29 | exec:
30 | command: ["sh", "-c", "rm -f /specular/workspace/.start_sp_magi.sh.lock"]
31 | resources:
32 | {{- .Values.default_resources | toYaml | nindent 10 }}
33 | volumeMounts:
34 | - mountPath: {{ .Values.volume.efs.mountPath }}
35 | name: {{ .Values.volume.efs.name }}
36 | {{- range $i, $value := .Values.volume.configVolumeMounts.paths }}
37 | - name: {{ $.Values.volume.configVolumeMounts.name }}
38 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value }}
39 | subPath: {{ $value }}
40 | {{- end }}
41 | {{- if not .Values.generator.deploy }}
42 | {{- range $i, $value := .Values.volume.secrets }}
43 | - name: secret-volume
44 | mountPath: {{ $.Values.volume.efs.mountPath }}/{{ $value.file }}
45 | subPath: {{ $value.file }}
46 | readOnly: true
47 | {{- end }}
48 | {{- end }}
49 | workingDir: {{ .Values.volume.efs.mountPath }}
50 | terminationGracePeriodSeconds: 60
51 | restartPolicy: OnFailure
52 | volumes:
53 | - name: {{ .Values.volume.efs.name }}
54 | persistentVolumeClaim:
55 | claimName: {{ .Values.volume.efs.name }}
56 | - name: {{ .Values.volume.configVolumeMounts.name }}
57 | configMap:
58 | name: {{ .Values.volume.configVolumeMounts.name }}
59 | {{- if not .Values.generator.deploy }}
60 | - name: secret-volume
61 | secret:
62 | secretName: l2-secrets
63 | {{- end }}
64 | status: {}
65 |
--------------------------------------------------------------------------------
/contracts/src/libraries/trie/SecureMerkleTrie.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | /* Library Imports */
5 | import {MerkleTrie} from "./MerkleTrie.sol";
6 |
7 | /**
8 | * @title SecureMerkleTrie
9 | * @notice SecureMerkleTrie is a thin wrapper around the MerkleTrie library that hashes the input
10 | * keys. Ethereum's state trie hashes input keys before storing them.
11 | */
12 | library SecureMerkleTrie {
13 | /**
14 | * @notice Verifies a proof that a given key/value pair is present in the Merkle trie.
15 | *
16 | * @param _key Key of the node to search for, as a hex string.
17 | * @param _value Value of the node to search for, as a hex string.
18 | * @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle
19 | * trees, this proof is executed top-down and consists of a list of RLP-encoded
20 | * nodes that make a path down to the target node.
21 | * @param _root Known root of the Merkle trie. Used to verify that the included proof is
22 | * correctly constructed.
23 | *
24 | * @return Whether or not the proof is valid.
25 | */
26 | function verifyInclusionProof(bytes memory _key, bytes memory _value, bytes[] memory _proof, bytes32 _root)
27 | internal
28 | pure
29 | returns (bool)
30 | {
31 | bytes memory key = _getSecureKey(_key);
32 | return MerkleTrie.verifyInclusionProof(key, _value, _proof, _root);
33 | }
34 |
35 | /**
36 | * @notice Retrieves the value associated with a given key.
37 | *
38 | * @param _key Key to search for, as hex bytes.
39 | * @param _proof Merkle trie inclusion proof for the key.
40 | * @param _root Known root of the Merkle trie.
41 | *
42 | * @return Whether or not the key exists.
43 | * @return Value of the key if it exists.
44 | */
45 | function get(bytes memory _key, bytes[] memory _proof, bytes32 _root) internal pure returns (bool, bytes memory) {
46 | bytes memory key = _getSecureKey(_key);
47 | return MerkleTrie.get(key, _proof, _root);
48 | }
49 |
50 | /**
51 | * @notice Computes the hashed version of the input key.
52 | *
53 | * @param _key Key to hash.
54 | *
55 | * @return Hashed version of the key.
56 | */
57 | function _getSecureKey(bytes memory _key) private pure returns (bytes memory) {
58 | return abi.encodePacked(keccak256(_key));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/contracts/scripts/e2e/test_transactions.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "hardhat";
2 | import { l1FeeRecipientAddress, l2BaseFeeRecipient } from "./addresses";
3 | import { getSignersAndContracts } from "./utils";
4 |
5 | async function main() {
6 | const { l2Provider, l2Relayer, l2Bridger } = await getSignersAndContracts();
7 |
8 | const value = ethers.utils.parseEther("0.1");
9 |
10 | const startBalances = {
11 | l2Relayer: await l2Relayer.getBalance(),
12 | l2Bridger: await l2Bridger.getBalance(),
13 | l1FeeRecipient: await l2Provider.getBalance(l1FeeRecipientAddress),
14 | l2BaseFeeRecipient: await l2Provider.getBalance(l2BaseFeeRecipient),
15 | };
16 |
17 | // TODO: should we randomize numTx and value?
18 | const numTx = 5;
19 | for (let i = 0; i < numTx; i++) {
20 | const tx = await l2Relayer.sendTransaction({
21 | to: l2Bridger.address,
22 | value,
23 | });
24 | await tx.wait();
25 | }
26 |
27 | const endBalances = {
28 | l2Relayer: await l2Relayer.getBalance(),
29 | l2Bridger: await l2Bridger.getBalance(),
30 | l1FeeRecipient: await l2Provider.getBalance(l1FeeRecipientAddress),
31 | l2BaseFeeRecipient: await l2Provider.getBalance(l2BaseFeeRecipient),
32 | };
33 |
34 | const totalValue = value.mul(numTx);
35 |
36 | if (!endBalances.l2Bridger.sub(startBalances.l2Bridger).eq(totalValue)) {
37 | console.log({ startBalances, endBalances, totalValue });
38 | throw `balance after transaction does not match the transaction amount on L2Bridge`;
39 | }
40 |
41 | // TODO: more precise check
42 | if (!endBalances.l1FeeRecipient.gt(startBalances.l1FeeRecipient)) {
43 | console.log({ startBalances, endBalances, totalValue });
44 | throw "did not collect L1 fee";
45 | }
46 |
47 | if (!endBalances.l2BaseFeeRecipient.gt(startBalances.l2BaseFeeRecipient)) {
48 | console.log({ startBalances, endBalances, totalValue });
49 | throw "did not collect L2 base fee";
50 | }
51 |
52 | console.log({ startBalances, endBalances, totalValue });
53 |
54 | const acceptableMargin = ethers.utils.parseEther("0.001");
55 | if (
56 | !startBalances.l2Relayer
57 | .sub(endBalances.l2Relayer)
58 | .sub(totalValue)
59 | .lt(acceptableMargin)
60 | ) {
61 | throw "balance after transaction does not match the transaction acceptable margin on L2Relay";
62 | }
63 |
64 | console.log("transactions test was successful");
65 | }
66 |
67 | main()
68 | .then(() => process.exit(0))
69 | .catch((error) => {
70 | console.error(error);
71 | process.exit(1);
72 | });
73 |
--------------------------------------------------------------------------------
/services/sidecar/rollup/rpc/eth/eth_syncer.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/ethereum/go-ethereum/core/types"
8 | "golang.org/x/sync/errgroup"
9 |
10 | "github.com/specularL2/specular/services/sidecar/utils"
11 | )
12 |
13 | // TODO: move to config
14 | const (
15 | EthSlotInterval = 12 * time.Second
16 | EthEpochInterval = 6*time.Minute + 24*time.Second
17 | )
18 |
19 | type EthSyncer struct {
20 | OnNewHandler
21 | LatestHeaderBroker *utils.Broker[*types.Header]
22 | SafeHeaderBroker *utils.Broker[*types.Header]
23 | FinalizedHeaderBroker *utils.Broker[*types.Header]
24 | eg errgroup.Group
25 | }
26 |
27 | type SyncerEthClient interface {
28 | HeaderByTag(ctx context.Context, tag BlockTag) (*types.Header, error)
29 | }
30 |
31 | type OnNewHandler interface {
32 | OnLatest(ctx context.Context, header *types.Header) error
33 | OnSafe(ctx context.Context, header *types.Header) error
34 | OnFinalized(ctx context.Context, header *types.Header) error
35 | }
36 |
37 | func NewEthSyncer(handler OnNewHandler) *EthSyncer {
38 | return &EthSyncer{
39 | OnNewHandler: handler,
40 | LatestHeaderBroker: utils.NewBroker[*types.Header](),
41 | SafeHeaderBroker: utils.NewBroker[*types.Header](),
42 | FinalizedHeaderBroker: utils.NewBroker[*types.Header](),
43 | }
44 | }
45 |
46 | // Starts a subscription in a separate goroutine for each commitment level.
47 | func (s *EthSyncer) Start(ctx context.Context, client SyncerEthClient) {
48 | s.subscribeNewHead(ctx, client, Latest, s.LatestHeaderBroker, s.OnLatest, EthSlotInterval)
49 | s.subscribeNewHead(ctx, client, Safe, s.SafeHeaderBroker, s.OnSafe, EthEpochInterval)
50 | s.subscribeNewHead(ctx, client, Finalized, s.FinalizedHeaderBroker, s.OnFinalized, EthEpochInterval)
51 | }
52 |
53 | func (s *EthSyncer) Stop(ctx context.Context) error {
54 | s.LatestHeaderBroker.Stop()
55 | s.FinalizedHeaderBroker.Stop()
56 | err := s.eg.Wait()
57 | if err != nil {
58 | return err
59 | }
60 | return nil
61 | }
62 |
63 | // Starts polling for new headers and publishes them to the broker.
64 | func (s *EthSyncer) subscribeNewHead(
65 | ctx context.Context,
66 | client SyncerEthClient,
67 | tag BlockTag,
68 | broker *utils.Broker[*types.Header],
69 | fn func(context.Context, *types.Header) error,
70 | pollInterval time.Duration,
71 | ) {
72 | sub := SubscribeNewHeadByPolling(ctx, client, broker.PubCh, tag, pollInterval, 10*time.Second)
73 | s.eg.Go(func() error { return broker.Start(ctx, sub) })
74 | broker.SubscribeWithCallback(ctx, fn)
75 | }
76 |
--------------------------------------------------------------------------------