├── 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 | --------------------------------------------------------------------------------