diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 1d195be1f..d9afdaabb 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -15,7 +15,7 @@ permissions: jobs: bench: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 container: image: ghcr.io/dojoengine/katana-dev:latest steps: diff --git a/.github/workflows/build-and-push-docker.yml b/.github/workflows/build-and-push-docker.yml index 97208b8ce..57efd77bf 100644 --- a/.github/workflows/build-and-push-docker.yml +++ b/.github/workflows/build-and-push-docker.yml @@ -28,15 +28,15 @@ jobs: # |--------------------|---------------------------| # setup: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 outputs: tag_name: ${{ steps.docker_tag.outputs.tag_name }} steps: - name: Checkout repository uses: actions/checkout@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + - name: Setup Blacksmith Builder + uses: useblacksmith/setup-docker-builder@v1 - name: Set Docker tag id: docker_tag @@ -49,7 +49,7 @@ jobs: fi build-and-push-amd64: - runs-on: ubuntu-latest-8-cores + runs-on: blacksmith-8vcpu-ubuntu-2404 needs: setup steps: - name: Checkout repository @@ -69,7 +69,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image for amd64 - uses: docker/build-push-action@v6 + uses: useblacksmith/build-push-action@v2 with: context: . push: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' }} @@ -79,10 +79,9 @@ jobs: RUST_VERSION=${{ env.RUST_VERSION }} CLIPPY_VERSION=${{ env.CLIPPY_VERSION }} platforms: linux/amd64 - cache-from: type=registry,ref=ghcr.io/${{ github.repository }}-dev:latest-amd64 build-and-push-arm64: - runs-on: ubuntu-latest-8-cores-arm64 + runs-on: blacksmith-8vcpu-ubuntu-2404-arm needs: setup steps: - name: Checkout repository @@ -102,7 +101,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image for arm64 - uses: docker/build-push-action@v6 + uses: useblacksmith/build-push-action@v2 with: context: . push: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' }} @@ -112,10 +111,9 @@ jobs: RUST_VERSION=${{ env.RUST_VERSION }} CLIPPY_VERSION=${{ env.CLIPPY_VERSION }} platforms: linux/arm64 - cache-from: type=registry,ref=ghcr.io/${{ github.repository }}-dev:latest-arm64 create-multiplatform-manifest: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 needs: [setup, build-and-push-amd64, build-and-push-arm64] steps: - name: Login to GitHub Container Registry diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 267a6090b..b370603b2 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -18,7 +18,7 @@ jobs: (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(vars.ALLOWED_CLAUDE_USERS, github.event.review.user.login)) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(vars.ALLOWED_CLAUDE_USERS, github.event.issue.user.login))) - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: write pull-requests: write @@ -66,7 +66,7 @@ jobs: github.event.comment && github.event.comment.body == '/claude-review' - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: write pull-requests: write diff --git a/.github/workflows/dockerfile-build-test.yml b/.github/workflows/dockerfile-build-test.yml index 184493f2b..abdd6ef5f 100644 --- a/.github/workflows/dockerfile-build-test.yml +++ b/.github/workflows/dockerfile-build-test.yml @@ -11,7 +11,7 @@ env: jobs: build-dev-image: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 strategy: matrix: platform: [linux/amd64] @@ -19,11 +19,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Setup Blacksmith Builder + uses: useblacksmith/setup-docker-builder@v1 - name: Test Docker build for ${{ matrix.platform }} - uses: docker/build-push-action@v5 + uses: useblacksmith/build-push-action@v2 with: push: false file: .github/Dockerfile @@ -31,5 +31,3 @@ jobs: build-args: | RUST_VERSION=${{ env.RUST_VERSION }} CLIPPY_VERSION=${{ env.CLIPPY_VERSION }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/generate-db-dispatch.yml b/.github/workflows/generate-db-dispatch.yml index 085eef18d..66da9ea1d 100644 --- a/.github/workflows/generate-db-dispatch.yml +++ b/.github/workflows/generate-db-dispatch.yml @@ -4,7 +4,7 @@ on: jobs: generate-database: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 container: image: ghcr.io/dojoengine/katana-dev:latest diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 5100cc8f9..01f83c1e6 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -26,7 +26,7 @@ jobs: permissions: pull-requests: write contents: write - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 container: image: ghcr.io/dojoengine/katana-dev:latest env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4c498d8d..ce9b0c0bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: prepare: # The prepare-release branch names comes from the release-dispatch.yml workflow. if: (github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'prepare-release') || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} steps: @@ -41,7 +41,7 @@ jobs: fi build-contracts: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 needs: prepare container: image: ghcr.io/dojoengine/katana-dev:latest @@ -76,23 +76,23 @@ jobs: # The arch is either 386, arm64 or amd64 # The svm target platform to use for the binary https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24 # Added native_build dimension to control build type - - os: ubuntu-latest-8-cores + - os: blacksmith-8vcpu-ubuntu-2404 platform: linux target: x86_64-unknown-linux-gnu arch: amd64 native_build: true - - os: ubuntu-latest-8-cores + - os: blacksmith-8vcpu-ubuntu-2404 platform: linux target: x86_64-unknown-linux-gnu arch: amd64 native_build: false - - os: ubuntu-latest-8-cores-arm64 + - os: blacksmith-8vcpu-ubuntu-2404-arm platform: linux target: aarch64-unknown-linux-gnu arch: arm64 svm_target_platform: linux-aarch64 native_build: true - - os: ubuntu-latest-8-cores-arm64 + - os: blacksmith-8vcpu-ubuntu-2404-arm platform: linux target: aarch64-unknown-linux-gnu arch: arm64 @@ -277,7 +277,7 @@ jobs: retention-days: 1 create-draft-release: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 needs: [prepare, release] container: image: ghcr.io/dojoengine/katana-dev:latest @@ -301,7 +301,7 @@ jobs: - run: gh release create ${{ steps.version_info.outputs.version }} ./artifacts/* --generate-notes --draft docker-build-and-push: - runs-on: ubuntu-latest-8-cores + runs-on: blacksmith-8vcpu-ubuntu-2404 needs: [prepare, release] steps: @@ -315,8 +315,8 @@ jobs: path: artifacts/linux merge-multiple: true - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + - name: Setup Blacksmith Builder + uses: useblacksmith/setup-docker-builder@v1 - name: Login to GitHub Container Registry uses: docker/login-action@v1 @@ -327,7 +327,7 @@ jobs: - name: Build and push docker image if: ${{ contains(needs.prepare.outputs.tag_name, 'preview') }} - uses: docker/build-push-action@v3 + uses: useblacksmith/build-push-action@v2 with: push: true tags: ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} @@ -337,7 +337,7 @@ jobs: - name: Build and push docker image if: ${{ !contains(needs.prepare.outputs.tag_name, 'preview') }} - uses: docker/build-push-action@v3 + uses: useblacksmith/build-push-action@v2 with: push: true tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.tag_name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a22be2289..789d4cd24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ env: jobs: fmt: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: image: ghcr.io/dojoengine/katana-dev:latest @@ -43,7 +43,7 @@ jobs: generate-test-artifacts: needs: [fmt] - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: image: ghcr.io/dojoengine/katana-dev:latest @@ -89,7 +89,7 @@ jobs: build-katana-binary: needs: [fmt, clippy, generate-test-artifacts] - runs-on: ubuntu-latest-32-cores + runs-on: blacksmith-32vcpu-ubuntu-2404 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: image: ghcr.io/dojoengine/katana-dev:latest @@ -128,7 +128,7 @@ jobs: clippy: needs: [generate-test-artifacts] - runs-on: ubuntu-latest-4-cores + runs-on: blacksmith-4vcpu-ubuntu-2404 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: image: ghcr.io/dojoengine/katana-dev:latest @@ -158,7 +158,7 @@ jobs: test: needs: [fmt, clippy, generate-test-artifacts, build-katana-binary] - runs-on: ubuntu-latest-32-cores + runs-on: blacksmith-32vcpu-ubuntu-2404 timeout-minutes: 30 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: @@ -216,7 +216,7 @@ jobs: snos-integration-test: needs: [fmt, clippy] - runs-on: ubuntu-latest-32-cores + runs-on: blacksmith-32vcpu-ubuntu-2404 timeout-minutes: 30 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: @@ -253,7 +253,7 @@ jobs: explorer-reverse-proxy: needs: [fmt, clippy, build-katana-binary] - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: image: ghcr.io/dojoengine/katana-dev:latest @@ -285,7 +285,7 @@ jobs: dojo-integration-test: needs: [fmt, clippy, build-katana-binary] - runs-on: ubuntu-latest-32-cores + runs-on: blacksmith-32vcpu-ubuntu-2404 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) container: image: ghcr.io/dojoengine/katana-dev:latest @@ -321,19 +321,30 @@ jobs: uses: actions/checkout@v3 with: repository: dojoengine/dojo - ref: v1.7.0-alpha.2 + ref: v1.7.0 path: dojo - uses: software-mansion/setup-scarb@v1 with: - scarb-version: "dev-2025-09-05" + scarb-version: "2.12.2" - - name: Build and migrate `spawn-and-move` project + # Note: We build sozo from source instead of downloading the prebuilt binary + # because the GitHub release artifacts require GLIBC 2.38/2.39 which is not + # available in our CI container (ghcr.io/dojoengine/katana-dev:latest) + # We need to use Rust 1.88+ due to clap dependency requirements in sozo v1.7.0 + - name: Install Rust 1.88 for sozo build + run: | + rustup toolchain install 1.88.0 + rustup default 1.88.0 + + - name: Build sozo from source run: | cd dojo cargo install --path bin/sozo --locked --force - cd examples/spawn-and-move + - name: Build and migrate `spawn-and-move` project + run: | + cd dojo/examples/spawn-and-move sozo build && sozo migrate - name: Output Katana logs on failure diff --git a/.github/workflows/weekly-report.yml b/.github/workflows/weekly-report.yml index d5fce260e..d469911ab 100644 --- a/.github/workflows/weekly-report.yml +++ b/.github/workflows/weekly-report.yml @@ -14,7 +14,7 @@ on: jobs: current-main-size: name: Current main branch binary size - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 container: image: ghcr.io/dojoengine/katana-dev:latest outputs: @@ -33,6 +33,9 @@ jobs: with: key: weekly-binary-size-main + - name: Make contracts + run: make contracts + - name: Get binary size (main) id: binary-size run: | @@ -45,7 +48,7 @@ jobs: latest-release-size: name: Latest release binary size - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 container: image: ghcr.io/dojoengine/katana-dev:latest outputs: @@ -64,6 +67,9 @@ jobs: with: key: weekly-binary-size-release + - name: Make contracts + run: make contracts + - name: Get latest release and binary size id: binary-size run: | @@ -85,7 +91,7 @@ jobs: generate-report: name: Generate binary size report needs: [current-main-size, latest-release-size] - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 container: image: ghcr.io/dojoengine/katana-dev:latest diff --git a/bin/katana/src/cli/init/deployment.rs b/bin/katana/src/cli/init/deployment.rs index 1c249f8ce..d8b4a07e0 100644 --- a/bin/katana/src/cli/init/deployment.rs +++ b/bin/katana/src/cli/init/deployment.rs @@ -234,7 +234,7 @@ pub async fn deploy_settlement_contract( // FINAL CHECKS // ----------------------------------------------------------------------- - check_program_info(chain_id, deployed_appchain_contract, account.provider()).await?; + check_program_info(chain_id, deployed_appchain_contract.into(), account.provider()).await?; Ok(DeploymentOutcome { block_number: deployment_block, @@ -262,10 +262,10 @@ pub async fn deploy_settlement_contract( /// * Layout bridge program hash pub async fn check_program_info( chain_id: Felt, - appchain_address: Felt, + appchain_address: ContractAddress, provider: &SettlementChainProvider, ) -> Result<(), ContractInitError> { - let appchain = AppchainContractReader::new(appchain_address, provider); + let appchain = AppchainContractReader::new(appchain_address.into(), provider); // Compute the chain's config hash let config_hash = compute_config_hash( diff --git a/bin/katana/src/cli/init/mod.rs b/bin/katana/src/cli/init/mod.rs index 3b6a087cd..3a863fac0 100644 --- a/bin/katana/src/cli/init/mod.rs +++ b/bin/katana/src/cli/init/mod.rs @@ -1,9 +1,66 @@ +//! Chain initialization commands for Katana. +//! +//! This module provides functionality to initialize new blockchain networks with Katana, +//! supporting both rollup and [sovereign] chain configurations. Currently, Katana only supports +//! deploying the rollup chain on top of the Starknet blockchain. +//! +//! # Overview +//! +//! The `init` command supports two distinct initialization modes: +//! +//! ## Rollup Mode (`katana init rollup`) +//! +//! Initializes a rollup chain that settles on an existing blockchain (right now only Starknet). +//! +//! **Interactive Usage:** +//! +//! ```bash +//! // Prompts for all required information when no flags are provided. +//! katana init rollup +//! ``` +//! +//! **Explicit Usage:** +//! +//! ```bash +//! katana init rollup \ +//! --id my-rollup \ +//! --settlement-chain sepolia \ +//! --settlement-account-address 0x123... \ +//! --settlement-account-private-key 0x456... +//! ``` +//! +//! ## Sovereign Mode (`katana init sovereign`) +//! +//! Initializes a sovereign chain that operates independently without settlement on another +//! blockchain. State updates and proofs are published to a Data Availability layer only. +//! +//! **Interactive Usage:** +//! +//! ```bash +//! // Prompts for all required information when no flags are provided. +//! katana init sovereign +//! ``` +//! +//! **Explicit Usage:** +//! +//! ```bash +//! katana init sovereign --id my-sovereign-chain +//! ``` +//! +//! # configuration output +//! +//! both modes generate chain specification files that can be used to start katana nodes: +//! - local configuration: `~/.config/katana/chains/` +//! - custom path: use `--output-path` to specify a directory +//! +//! [sovereign]: https://celestia.org/learn/intermediates/sovereign-rollups-an-introduction/ + use std::path::PathBuf; use std::str::FromStr; use anyhow::Context; use clap::builder::NonEmptyStringValueParser; -use clap::Args; +use clap::{Args, Subcommand}; use deployment::DeploymentOutcome; use katana_chain_spec::rollup::{ChainConfigDir, FeeContract}; use katana_chain_spec::{rollup, SettlementLayer}; @@ -27,42 +84,80 @@ mod settlement; mod slot; #[derive(Debug, Args)] -pub struct InitArgs { +pub struct InitCommand { + #[command(subcommand)] + pub mode: InitMode, +} + +/// initialization mode selection for different chain types. +#[derive(Debug, Subcommand)] +pub enum InitMode { + #[command(about = "Initialize a rollup chain")] + Rollup(Box), + + #[command(hide = true)] + #[command(about = "Initialize a sovereign chain")] + Sovereign(SovereignArgs), +} + +/// Configuration arguments for rollup chain initialization. +/// +/// Rollup chains settle their state and proofs on an existing Layer 1 blockchain. +/// This requires settlement layer connectivity, account management, and contract deployment. +/// +/// ## Interactivity +/// +/// If no arguments are provided, the command will prompt interactively for them. +#[derive(Debug, Args)] +pub struct RollupArgs { /// The id of the new chain to be initialized. /// /// An empty `Id` is not a allowed, since the chain id must be /// a valid ASCII string. #[arg(long)] #[arg(value_parser = NonEmptyStringValueParser::new())] + #[arg(requires_all = ["settlement_chain", "settlement_account", "settlement_account_private_key"])] id: Option, - /// The settlement chain to be used, where the core contract is deployed. - /// - /// If a custom settlement chain is provided, setting a custom facts registry is required using - /// the `--settlement-facts-registry` option. Otherwise, setting a custom facts registry - /// with a known chain is a no-op. - #[arg(long = "settlement-chain")] - #[arg(required_unless_present = "sovereign")] - #[arg(requires_all = ["id", "settlement_account", "settlement_account_private_key"])] + #[arg( + long = "settlement-chain", + help = "The settlement chain to be used, where the core contract is deployed." + )] + #[arg(long_help = "The settlement chain to be used, where the core contract is deployed. + +Possible values: + - `mainnet`, `sn_mainnet`: Starknet mainnet + - `sepolia`, `sn_sepolia`: Starknet sepolia")] + #[cfg_attr( + feature = "init-custom-settlement-chain", + arg(long_help = "The settlement chain to be used, where the core contract is deployed. + +Possible values: + - `mainnet`, `sn_mainnet`: Starknet mainnet + - `sepolia`, `sn_sepolia`: Starknet sepolia + - : Custom settlement chain URL (requires --settlement-facts-registry) + +If a custom settlement chain is provided, setting a custom facts registry is required using +the `--settlement-facts-registry` option. Otherwise, setting a custom facts registry +with a known chain is a no-op for now.") + )] + #[arg(requires = "id")] settlement_chain: Option, - /// The address of the settlement account to be used to configure the core contract. #[arg(long = "settlement-account-address")] - #[arg(required_unless_present = "sovereign")] - #[arg(requires_all = ["id", "settlement_chain", "settlement_account_private_key"])] + #[arg(requires = "id")] settlement_account: Option, /// The private key of the settlement account to be used to configure the core contract. #[arg(long = "settlement-account-private-key")] - #[arg(required_unless_present = "sovereign")] - #[arg(requires_all = ["id", "settlement_chain", "settlement_account"])] + #[arg(requires = "id")] settlement_account_private_key: Option, /// The address of the settlement contract. /// If not provided, the contract will be deployed on the settlement chain using the provided /// settlement account. #[arg(long = "settlement-contract")] - #[arg(requires_all = ["id", "settlement_chain", "settlement_account", "settlement_account_private_key", "settlement_contract_deployed_block"])] + #[arg(requires_all = ["id", "settlement_contract_deployed_block"])] settlement_contract: Option, /// The block number of the settlement contract deployment. @@ -76,18 +171,26 @@ pub struct InitArgs { /// /// Required if a custom settlement chain is specified. #[arg(long = "settlement-facts-registry")] - #[arg(requires_all = ["id", "settlement_chain", "settlement_account"])] - pub settlement_facts_registry_contract: Option, + settlement_facts_registry_contract: Option, - /// Initialize a sovereign chain with no settlement layer, by only publishing the state updates - /// and proofs on a Data Availability Layer. By using this flag, no settlement option is - /// required. + /// Specify the path of the directory where the configuration files will be stored at. #[arg(long)] - #[arg(help = "Initialize a sovereign chain with no settlement layer, by only publishing the \ - state updates and proofs on a Data Availability Layer.")] - #[arg(requires_all = ["id"])] - #[arg(conflicts_with_all = ["settlement_chain", "settlement_account", "settlement_account_private_key", "settlement_contract"])] - sovereign: bool, + output_path: Option, + + #[cfg(feature = "init-slot")] + #[command(flatten)] + slot: slot::SlotArgs, +} + +#[derive(Debug, Args)] +pub struct SovereignArgs { + /// The id of the new chain to be initialized. + /// + /// An empty `Id` is not a allowed, since the chain id must be + /// a valid ASCII string. + #[arg(long)] + #[arg(value_parser = NonEmptyStringValueParser::new())] + id: Option, /// Specify the path of the directory where the configuration files will be stored at. #[arg(long)] @@ -98,36 +201,45 @@ pub struct InitArgs { slot: slot::SlotArgs, } -impl InitArgs { - // TODO: - // - deploy bridge contract +impl InitCommand { + /// Executes the initialization command based on the selected mode. + /// + /// Dispatches to the appropriate initialization logic for either rollup or sovereign chains. + pub(crate) async fn execute(self) -> anyhow::Result<()> { + match self.mode { + InitMode::Rollup(args) => args.execute().await, + InitMode::Sovereign(args) => args.execute().await, + } + } +} + +impl RollupArgs { + /// Executes rollup chain initialization with settlement layer integration. + /// + /// # Interactive Behavior + /// + /// Falls back to interactive prompts when no CLI flags are provided. pub(crate) async fn execute(self) -> anyhow::Result<()> { let output = if let Some(output) = self.configure_from_args().await { output? } else { - prompt::prompt().await? + prompt::prompt_rollup().await? }; - let settlement = match &output { - AnyOutcome::Persistent(persistent) => SettlementLayer::Starknet { - account: persistent.account, - rpc_url: persistent.rpc_url.clone(), - id: ChainId::parse(&persistent.settlement_id)?, - block: persistent.deployment_outcome.block_number, - core_contract: persistent.deployment_outcome.contract_address, - }, - AnyOutcome::Sovereign(_) => SettlementLayer::Sovereign {}, + let settlement = SettlementLayer::Starknet { + account: output.account, + rpc_url: output.rpc_url.clone(), + id: ChainId::parse(&output.settlement_id)?, + block: output.deployment_outcome.block_number, + core_contract: output.deployment_outcome.contract_address, }; - let id = ChainId::parse(output.id())?; + let id = ChainId::parse(&output.id)?; #[cfg_attr(not(feature = "init-slot"), allow(unused_mut))] let mut genesis = generate_genesis(); #[cfg(feature = "init-slot")] - slot::add_paymasters_to_genesis( - &mut genesis, - &output.slot_paymasters().unwrap_or_default(), - ); + slot::add_paymasters_to_genesis(&mut genesis, &output.slot_paymasters.unwrap_or_default()); // At the moment, the fee token is limited to a predefined token. let fee_contract = FeeContract::default(); @@ -145,23 +257,20 @@ impl InitArgs { Ok(()) } - async fn configure_from_args(&self) -> Option> { + async fn configure_from_args(&self) -> Option> { if let Some(id) = self.id.clone() { - if self.sovereign { - return Some(Ok(AnyOutcome::Sovereign(SovereignOutcome { - id, - #[cfg(feature = "init-slot")] - slot_paymasters: self.slot.paymaster_accounts.clone(), - }))); - } - - // These args are all required if at least one of them are specified (incl chain id) and - // `clap` has already handled that for us, so it's safe to unwrap here. - let settlement_chain = self.settlement_chain.clone().expect("must present"); - let settlement_account_address = self.settlement_account.expect("must present"); - let settlement_private_key = self.settlement_account_private_key.expect("must present"); + // Check if all required settlement args are provided + let Some(settlement_chain) = self.settlement_chain.clone() else { + return None; // Fall back to prompting + }; + let Some(settlement_account_address) = self.settlement_account else { + return None; // Fall back to prompting + }; + let Some(settlement_private_key) = self.settlement_account_private_key else { + return None; // Fall back to prompting + }; - let settlement_provider = match settlement_chain { + let settlement_provider = match &settlement_chain { SettlementChain::Mainnet => { let mut provider = SettlementChainProvider::sn_mainnet(); if let Some(fact_registry) = self.settlement_facts_registry_contract { @@ -185,18 +294,32 @@ impl InitArgs { chain" ))); }; - SettlementChainProvider::new(url, *fact_registry) + SettlementChainProvider::new(url.clone(), *fact_registry) } }; - let l1_chain_id = settlement_provider.chain_id().await.unwrap(); + let l1_chain_id = match settlement_provider.chain_id().await.with_context(|| { + format!("failed to get chain id for settlement layer `{settlement_chain}`") + }) { + Ok(id) => id, + Err(err) => return Some(Err(err)), + }; - let chain_id = cairo_short_string_to_felt(&id).unwrap(); + let chain_id = match cairo_short_string_to_felt(&id) + .with_context(|| format!("invalid chain id: {id}")) + { + Ok(id) => id, + Err(err) => return Some(Err(err)), + }; let deployment_outcome = if let Some(contract) = self.settlement_contract { - deployment::check_program_info(chain_id, contract.into(), &settlement_provider) + match deployment::check_program_info(chain_id, contract, &settlement_provider) .await - .unwrap(); + .with_context(|| "settlement contract validation failed.".to_string()) + { + Ok(..) => (), + Err(err) => return Some(Err(err)), + }; DeploymentOutcome { contract_address: contract, @@ -215,10 +338,16 @@ impl InitArgs { ExecutionEncoding::New, ); - deployment::deploy_settlement_contract(account, chain_id).await.unwrap() + match deployment::deploy_settlement_contract(account, chain_id) + .await + .with_context(|| "failed to deploy settlement contract".to_string()) + { + Ok(id) => id, + Err(err) => return Some(Err(err)), + } }; - Some(Ok(AnyOutcome::Persistent(PersistentOutcome { + Some(Ok(PersistentOutcome { id, deployment_outcome, rpc_url: settlement_provider.url().clone(), @@ -226,34 +355,52 @@ impl InitArgs { settlement_id: parse_cairo_short_string(&l1_chain_id).unwrap(), #[cfg(feature = "init-slot")] slot_paymasters: self.slot.paymaster_accounts.clone(), - }))) + })) } else { None } } } -/// The outcome of the initialization process. -#[derive(Debug)] -enum AnyOutcome { - Persistent(PersistentOutcome), - Sovereign(SovereignOutcome), -} +impl SovereignArgs { + /// Executes sovereign chain initialization. + pub(crate) async fn execute(self) -> anyhow::Result<()> { + let output = if let Some(output) = self.configure_from_args() { + output + } else { + prompt::prompt_sovereign().await? + }; + + let settlement = SettlementLayer::Sovereign {}; + let id = ChainId::parse(&output.id)?; -impl AnyOutcome { - pub fn id(&self) -> &str { - match self { - AnyOutcome::Persistent(persistent) => &persistent.id, - AnyOutcome::Sovereign(sovereign) => &sovereign.id, + #[cfg_attr(not(feature = "init-slot"), allow(unused_mut))] + let mut genesis = generate_genesis(); + #[cfg(feature = "init-slot")] + slot::add_paymasters_to_genesis(&mut genesis, &output.slot_paymasters.unwrap_or_default()); + + // At the moment, the fee token is limited to a predefined token. + let fee_contract = FeeContract::default(); + let chain_spec = rollup::ChainSpec { id, genesis, settlement, fee_contract }; + + if let Some(path) = self.output_path { + let dir = ChainConfigDir::create(path)?; + rollup::write(&dir, &chain_spec).context("failed to write chain spec file")?; + } else { + // Write to the local chain config directory by default if user + // doesn't specify the output path + rollup::write_local(&chain_spec).context("failed to write chain spec file")?; } + + Ok(()) } - #[cfg(feature = "init-slot")] - pub fn slot_paymasters(&self) -> Option> { - match self { - AnyOutcome::Persistent(persistent) => persistent.slot_paymasters.clone(), - AnyOutcome::Sovereign(sovereign) => sovereign.slot_paymasters.clone(), - } + fn configure_from_args(&self) -> Option { + self.id.clone().map(|id| SovereignOutcome { + id, + #[cfg(feature = "init-slot")] + slot_paymasters: self.slot.paymaster_accounts.clone(), + }) } } @@ -302,6 +449,7 @@ struct SettlementChainTryFromStrError { id: String, } +/// Supported settlement chain options for rollup initialization. #[derive(Debug, Clone, strum_macros::Display, PartialEq, Eq)] enum SettlementChain { Mainnet, @@ -312,6 +460,7 @@ enum SettlementChain { impl std::str::FromStr for SettlementChain { type Err = SettlementChainTryFromStrError; + fn from_str(s: &str) -> Result::Err> { let id = s.to_lowercase(); if &id == "sepolia" || &id == "sn_sepolia" { @@ -333,6 +482,7 @@ impl std::str::FromStr for SettlementChain { impl TryFrom<&str> for SettlementChain { type Error = SettlementChainTryFromStrError; + fn try_from(s: &str) -> Result>::Error> { SettlementChain::from_str(s) } @@ -381,7 +531,7 @@ mod tests { #[derive(Parser)] struct Cli { #[command(flatten)] - args: InitArgs, + args: InitCommand, } // This should fail with the expected error message:- @@ -392,7 +542,7 @@ mod tests { // --settlement-account-address // --settlement-account-private-key // ``` - match Cli::try_parse_from(["init", "--id", "bruh"]) { + match Cli::try_parse_from(["init", "rollup", "--id", "bruh"]) { Ok(..) => panic!("Expected parsing to fail with missing required arguments"), Err(err) => { if let ContextValue::Strings(values) = err.get(ContextKind::InvalidArg).unwrap() { @@ -417,10 +567,14 @@ mod tests { #[derive(Parser)] struct Cli { #[command(flatten)] - args: InitArgs, + args: InitCommand, } - Cli::parse_from(["init", "--id", "bruh", "--sovereign"]); + let result = Cli::parse_from(["init", "sovereign", "--id", "bruh"]); + + assert_matches!(result.args.mode, InitMode::Sovereign(config) => { + assert_eq!(config.id, Some("bruh".to_string())); + }); } #[test] @@ -428,12 +582,13 @@ mod tests { #[derive(Parser)] struct Cli { #[command(flatten)] - args: InitArgs, + args: InitCommand, } let custom_settlement_fact_registry = "0x1234567890123456789012345678901234567890"; let result = Cli::parse_from([ "init", + "rollup", "--id", "wot", "--settlement-chain", @@ -445,10 +600,13 @@ mod tests { "--settlement-facts-registry", custom_settlement_fact_registry, ]); - assert_eq!( - result.args.settlement_facts_registry_contract, - Some(ContractAddress::from_str(custom_settlement_fact_registry).unwrap()) - ); + + assert_matches!(result.args.mode, InitMode::Rollup(config) => { + assert_eq!( + config.settlement_facts_registry_contract, + Some(ContractAddress::from_str(custom_settlement_fact_registry).unwrap()) + ); + }); } #[test] @@ -456,7 +614,7 @@ mod tests { #[derive(Parser)] struct Cli { #[command(flatten)] - args: InitArgs, + args: InitCommand, } // This should fail with the expected error message:- @@ -469,6 +627,7 @@ mod tests { // ``` match Cli::try_parse_from([ "init", + "rollup", "--id", "wot", "--settlement-facts-registry", @@ -499,11 +658,12 @@ mod tests { #[derive(Parser)] struct Cli { #[command(flatten)] - args: InitArgs, + args: InitCommand, } let result = Cli::parse_from([ "init", + "rollup", "--id", "wot", "--settlement-chain", @@ -513,16 +673,18 @@ mod tests { "--settlement-account-private-key", "0x1234567890123456789012345678901234567890", ]); - assert_eq!(result.args.settlement_facts_registry_contract, None); - - let configure_result = result.args.configure_from_args().await; - assert!(configure_result.is_some()); - let configure_result = configure_result.unwrap(); - assert!(configure_result.is_err()); - assert_eq!( - configure_result.unwrap_err().to_string(), - "Specifying the facts registry contract (using `--settlement-facts-registry`) is \ - required when settling on a custom chain" - ); + assert_matches!(result.args.mode, InitMode::Rollup(config) => { + assert_eq!(config.settlement_facts_registry_contract, None); + + let configure_result = config.configure_from_args().await; + assert!(configure_result.is_some()); + let configure_result = configure_result.unwrap(); + assert!(configure_result.is_err()); + assert_eq!( + configure_result.unwrap_err().to_string(), + "Specifying the facts registry contract (using `--settlement-facts-registry`) is \ + required when settling on a custom chain" + ); + }); } } diff --git a/bin/katana/src/cli/init/prompt.rs b/bin/katana/src/cli/init/prompt.rs index d6cbd76b6..6d4f60cc8 100644 --- a/bin/katana/src/cli/init/prompt.rs +++ b/bin/katana/src/cli/init/prompt.rs @@ -14,12 +14,12 @@ use starknet::providers::Provider; use starknet::signers::{LocalWallet, SigningKey}; use tokio::runtime::Handle; -use super::{deployment, AnyOutcome, PersistentOutcome, SovereignOutcome}; +use super::{deployment, PersistentOutcome, SovereignOutcome}; use crate::cli::init::deployment::DeploymentOutcome; use crate::cli::init::settlement::SettlementChainProvider; use crate::cli::init::slot::{self, PaymasterAccountArgs}; -pub async fn prompt() -> Result { +pub async fn prompt_rollup() -> Result { let chain_id = CustomType::::new("Id") .with_help_message("This will be the id of your rollup chain.") // checks that the input is a valid ascii string. @@ -37,7 +37,6 @@ pub async fn prompt() -> Result { enum SettlementChainOpt { Mainnet, Sepolia, - Sovereign, #[cfg(feature = "init-custom-settlement-chain")] Custom, } @@ -49,7 +48,6 @@ pub async fn prompt() -> Result { let network_opts = vec![ SettlementChainOpt::Mainnet, SettlementChainOpt::Sepolia, - SettlementChainOpt::Sovereign, #[cfg(feature = "init-custom-settlement-chain")] SettlementChainOpt::Custom, ]; @@ -61,16 +59,6 @@ pub async fn prompt() -> Result { let settlement_provider = match network_type { SettlementChainOpt::Mainnet => SettlementChainProvider::sn_mainnet(), SettlementChainOpt::Sepolia => SettlementChainProvider::sn_sepolia(), - - SettlementChainOpt::Sovereign => { - let slot_paymasters = prompt_slot_paymasters()?; - return Ok(AnyOutcome::Sovereign(SovereignOutcome { - id: chain_id, - #[cfg(feature = "init-slot")] - slot_paymasters, - })); - } - // Useful for testing the program flow without having to run it against actual network. #[cfg(feature = "init-custom-settlement-chain")] SettlementChainOpt::Custom => { @@ -140,38 +128,35 @@ pub async fn prompt() -> Result { // The core settlement contract on L1c. // Prompt the user whether to deploy the settlement contract or not. - let deployment_outcome = if Confirm::new("Deploy settlement contract?") - .with_default(true) - .prompt()? - { - let chain_id = cairo_short_string_to_felt(&chain_id)?; - deployment::deploy_settlement_contract(account, chain_id).await? - } - // If denied, prompt the user for an already deployed contract. - else { - let address = CustomType::::new("Settlement contract") - .with_parser(contract_exist_parser) - .prompt()?; + let deployment_outcome = + if Confirm::new("Deploy settlement contract?").with_default(true).prompt()? { + let chain_id = cairo_short_string_to_felt(&chain_id)?; + deployment::deploy_settlement_contract(account, chain_id).await? + } + // If denied, prompt the user for an already deployed contract. + else { + let address = CustomType::::new("Settlement contract") + .with_parser(contract_exist_parser) + .prompt()?; - // Check that the settlement contract has been initialized with the correct program - // info. - let chain_id = cairo_short_string_to_felt(&chain_id)?; - deployment::check_program_info(chain_id, address.into(), &settlement_provider) - .await - .context( + // Check that the settlement contract has been initialized with the correct program + // info. + let chain_id = cairo_short_string_to_felt(&chain_id)?; + deployment::check_program_info(chain_id, address, &settlement_provider).await.context( "Invalid settlement contract. The contract might have been configured incorrectly.", )?; - let block_number = CustomType::::new("Settlement contract deployment block") - .with_help_message("The block at which the settlement contract was deployed") - .prompt()?; + let block_number = + CustomType::::new("Settlement contract deployment block") + .with_help_message("The block at which the settlement contract was deployed") + .prompt()?; - DeploymentOutcome { contract_address: address, block_number } - }; + DeploymentOutcome { contract_address: address, block_number } + }; let slot_paymasters = prompt_slot_paymasters()?; - Ok(AnyOutcome::Persistent(PersistentOutcome { + Ok(PersistentOutcome { id: chain_id, deployment_outcome, rpc_url: settlement_provider.url().clone(), @@ -179,7 +164,30 @@ pub async fn prompt() -> Result { settlement_id: parse_cairo_short_string(&l1_chain_id)?, #[cfg(feature = "init-slot")] slot_paymasters, - })) + }) +} + +pub async fn prompt_sovereign() -> Result { + let chain_id = CustomType::::new("Id") + .with_help_message("This will be the id of your sovereign chain.") + // checks that the input is a valid ascii string. + .with_parser(&|input| { + if !input.is_empty() && input.is_ascii() { + Ok(input.to_string()) + } else { + Err(()) + } + }) + .with_error_message("Must be valid ASCII characters") + .prompt()?; + + let slot_paymasters = prompt_slot_paymasters()?; + + Ok(SovereignOutcome { + id: chain_id, + #[cfg(feature = "init-slot")] + slot_paymasters, + }) } fn prompt_slot_paymasters() -> Result>> { diff --git a/bin/katana/src/cli/mod.rs b/bin/katana/src/cli/mod.rs index 7c8949b2f..759a2cef4 100644 --- a/bin/katana/src/cli/mod.rs +++ b/bin/katana/src/cli/mod.rs @@ -46,7 +46,7 @@ impl Cli { #[derive(Debug, Subcommand)] enum Commands { #[command(about = "Initialize chain")] - Init(Box), + Init(Box), #[command(about = "Chain configuration utilities")] Config(config::ConfigArgs), diff --git a/bin/katana/src/main.rs b/bin/katana/src/main.rs index b11b1075f..bb6ee33f1 100644 --- a/bin/katana/src/main.rs +++ b/bin/katana/src/main.rs @@ -2,7 +2,7 @@ use clap::Parser; fn main() { if let Err(err) = katana::cli::Cli::parse().run() { - eprintln!("\x1b[31merror:\x1b[0m {err}"); + eprintln!("\x1b[31merror:\x1b[0m {err:?}"); std::process::exit(1); } } diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 01e06f5af..e9279b3d6 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -4,6 +4,7 @@ license.workspace = true name = "katana-contracts" repository.workspace = true version.workspace = true +build = "build.rs" [dependencies] katana-contracts-macro = { path = "macro" } diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index f083a3125..12eb90338 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -3,7 +3,15 @@ use std::process::Command; use std::{env, fs}; fn main() { - println!("cargo:rerun-if-changed=contracts/"); + // Track specific source directories and files that should trigger a rebuild + // Important: We don't track Scarb.lock as scarb will update it on every `scarb build` + println!("cargo:rerun-if-changed=contracts/Scarb.toml"); + println!("cargo:rerun-if-changed=contracts/account"); + println!("cargo:rerun-if-changed=contracts/legacy"); + println!("cargo:rerun-if-changed=contracts/messaging"); + println!("cargo:rerun-if-changed=contracts/test-contracts"); + println!("cargo:rerun-if-changed=contracts/vrf"); + println!("cargo:rerun-if-changed=build.rs"); let contracts_dir = Path::new("contracts"); let target_dir = contracts_dir.join("target/dev"); @@ -21,7 +29,7 @@ fn main() { return; } - // Only build if we're not in a docs build or if explicitly requested + // Only build if we're not in a docs build if env::var("DOCS_RS").is_ok() { return; } diff --git a/crates/executor/src/implementation/blockifier/utils.rs b/crates/executor/src/implementation/blockifier/utils.rs index de7a4b391..dde58960d 100644 --- a/crates/executor/src/implementation/blockifier/utils.rs +++ b/crates/executor/src/implementation/blockifier/utils.rs @@ -588,8 +588,8 @@ fn to_api_resource_bounds(resource_bounds: fee::ResourceBoundsMapping) -> ValidR } fee::ResourceBoundsMapping::L1Gas(bounds) => ValidResourceBounds::L1Gas(ResourceBounds { - max_amount: bounds.max_amount.into(), - max_price_per_unit: bounds.max_price_per_unit.into(), + max_amount: bounds.l1_gas.max_amount.into(), + max_price_per_unit: bounds.l1_gas.max_price_per_unit.into(), }), } } @@ -723,7 +723,7 @@ pub fn is_zero_resource_bounds(resource_bounds: &ResourceBoundsMapping) -> bool } ResourceBoundsMapping::L1Gas(bounds) => { - (bounds.max_amount as u128 * bounds.max_price_per_unit) == 0 + (bounds.l1_gas.max_amount as u128 * bounds.l1_gas.max_price_per_unit) == 0 } } } diff --git a/crates/feeder-gateway/src/types/transaction.rs b/crates/feeder-gateway/src/types/transaction.rs index 77dcad1e6..49046ce19 100644 --- a/crates/feeder-gateway/src/types/transaction.rs +++ b/crates/feeder-gateway/src/types/transaction.rs @@ -1,6 +1,9 @@ use katana_primitives::class::{ClassHash, CompiledClassHash}; use katana_primitives::contract::Nonce; -use katana_primitives::fee::{AllResourceBoundsMapping, Tip}; +use katana_primitives::fee::{ + AllResourceBoundsMapping, L1GasResourceBoundsMapping, ResourceBounds, ResourceBoundsMapping, + Tip, +}; use katana_primitives::transaction::{ DeclareTx as PrimitiveDeclareTx, DeclareTxV0 as PrimitiveDeclareTxV0, DeclareTxV1 as PrimitiveDeclareTxV1, DeclareTxV2 as PrimitiveDeclareTxV2, @@ -12,7 +15,7 @@ use katana_primitives::transaction::{ TxWithHash, }; use katana_primitives::{ContractAddress, Felt}; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; #[derive(Debug, PartialEq, Eq, Deserialize)] pub struct ConfirmedTransaction { @@ -70,29 +73,6 @@ impl<'de> Deserialize<'de> for DataAvailabilityMode { } } -// Same reason as `DataAvailabilityMode` above, this struct is also defined because the serde -// implementation of its primitive counterpart is different. -#[derive(Debug, Default, PartialEq, Eq, Deserialize)] -pub struct ResourceBounds { - #[serde(deserialize_with = "serde_utils::deserialize_u64")] - pub max_amount: u64, - #[serde(deserialize_with = "serde_utils::deserialize_u128")] - pub max_price_per_unit: u128, -} - -#[derive(Debug, PartialEq, Eq, Deserialize)] -pub struct ResourceBoundsMapping { - #[serde(rename = "L1_GAS")] - pub l1_gas: ResourceBounds, - - #[serde(rename = "L2_GAS")] - pub l2_gas: ResourceBounds, - /// Marked as optional because prior to 0.13.4, L1 data gas is not a required field in the - /// resource bounds. - #[serde(rename = "L1_DATA_GAS")] - pub l1_data_gas: Option, -} - /// Invoke transaction enum with version-specific variants #[derive(Debug, PartialEq, Eq, Deserialize)] #[serde(tag = "version")] @@ -116,6 +96,7 @@ pub struct InvokeTxV3 { pub nonce: Nonce, pub calldata: Vec, pub signature: Vec, + #[serde(deserialize_with = "deserialize_resource_bounds_mapping")] pub resource_bounds: ResourceBoundsMapping, pub tip: Tip, pub paymaster_data: Vec, @@ -152,6 +133,7 @@ pub struct DeclareTxV3 { pub signature: Vec, pub class_hash: ClassHash, pub compiled_class_hash: CompiledClassHash, + #[serde(deserialize_with = "deserialize_resource_bounds_mapping")] pub resource_bounds: ResourceBoundsMapping, pub tip: Tip, pub paymaster_data: Vec, @@ -191,6 +173,7 @@ pub struct DeployAccountTxV3 { pub contract_address: Option, pub contract_address_salt: Felt, pub constructor_calldata: Vec, + #[serde(deserialize_with = "deserialize_resource_bounds_mapping")] pub resource_bounds: ResourceBoundsMapping, pub tip: Tip, pub paymaster_data: Vec, @@ -270,7 +253,7 @@ impl TryFrom for PrimitiveInvokeTx { calldata: tx.calldata, signature: tx.signature, sender_address: tx.sender_address, - resource_bounds: tx.resource_bounds.into(), + resource_bounds: tx.resource_bounds, account_deployment_data: tx.account_deployment_data, fee_data_availability_mode: tx.fee_data_availability_mode.into(), nonce_data_availability_mode: tx.nonce_data_availability_mode.into(), @@ -315,7 +298,7 @@ impl TryFrom for PrimitiveDeclareTx { signature: tx.signature, class_hash: tx.class_hash, compiled_class_hash: tx.compiled_class_hash, - resource_bounds: tx.resource_bounds.into(), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), paymaster_data: tx.paymaster_data, account_deployment_data: tx.account_deployment_data, @@ -352,7 +335,7 @@ impl TryFrom for PrimitiveDeployAccountTx { contract_address: tx.contract_address.unwrap_or_default(), contract_address_salt: tx.contract_address_salt, constructor_calldata: tx.constructor_calldata, - resource_bounds: tx.resource_bounds.into(), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), paymaster_data: tx.paymaster_data, nonce_data_availability_mode: tx.nonce_data_availability_mode.into(), @@ -387,28 +370,34 @@ impl From for katana_primitives::da::DataAvailabilityMode } } -impl From for katana_primitives::fee::ResourceBoundsMapping { - fn from(bounds: ResourceBoundsMapping) -> Self { - if let Some(l1_data_gas) = bounds.l1_data_gas { - Self::All(AllResourceBoundsMapping { - l1_gas: katana_primitives::fee::ResourceBounds { - max_amount: bounds.l1_gas.max_amount, - max_price_per_unit: bounds.l1_gas.max_price_per_unit, - }, - l2_gas: katana_primitives::fee::ResourceBounds { - max_amount: bounds.l2_gas.max_amount, - max_price_per_unit: bounds.l2_gas.max_price_per_unit, - }, - l1_data_gas: katana_primitives::fee::ResourceBounds { - max_amount: l1_data_gas.max_amount, - max_price_per_unit: l1_data_gas.max_price_per_unit, - }, - }) - } else { - Self::L1Gas(katana_primitives::fee::ResourceBounds { - max_amount: bounds.l1_gas.max_amount, - max_price_per_unit: bounds.l1_gas.max_price_per_unit, - }) - } +fn deserialize_resource_bounds_mapping<'de, D>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct FeederGatewayResourceBounds { + #[serde(rename = "L1_GAS")] + l1_gas: ResourceBounds, + #[serde(rename = "L2_GAS")] + l2_gas: ResourceBounds, + #[serde(rename = "L1_DATA_GAS")] + l1_data_gas: Option, + } + + let bounds = FeederGatewayResourceBounds::deserialize(deserializer)?; + + if let Some(l1_data_gas) = bounds.l1_data_gas { + Ok(ResourceBoundsMapping::All(AllResourceBoundsMapping { + l1_gas: bounds.l1_gas, + l2_gas: bounds.l2_gas, + l1_data_gas, + })) + } else { + Ok(ResourceBoundsMapping::L1Gas(L1GasResourceBoundsMapping { + l1_gas: bounds.l1_gas, + l2_gas: bounds.l2_gas, + })) } } diff --git a/crates/primitives/src/fee.rs b/crates/primitives/src/fee.rs index 9ae91b1c1..2be066ada 100644 --- a/crates/primitives/src/fee.rs +++ b/crates/primitives/src/fee.rs @@ -1,6 +1,5 @@ #[derive(Debug, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ResourceBounds { /// The max amount of the resource that can be used in the tx pub max_amount: u64, @@ -12,23 +11,33 @@ impl ResourceBounds { pub const ZERO: Self = Self { max_amount: 0, max_price_per_unit: 0 }; } -// Aliased to match the feeder gateway API #[derive(Debug, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AllResourceBoundsMapping { /// L1 gas bounds - covers L2→L1 messages sent by the transaction - #[serde(alias = "L1_GAS")] pub l1_gas: ResourceBounds, /// L2 gas bounds - covers L2 resources including computation, tx payload, event emission, code /// size, etc. Units: 1 Cairo step = 100 L2 gas - #[serde(alias = "L2_GAS")] pub l2_gas: ResourceBounds, /// L1 data gas (blob gas) bounds - covers the cost of submitting state diffs as blobs on L1 - #[serde(alias = "L1_DATA_GAS")] pub l1_data_gas: ResourceBounds, } +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct L1GasResourceBoundsMapping { + /// L1 gas bounds - covers L2→L1 messages sent by the transaction + pub l1_gas: ResourceBounds, + + /// L2 gas bounds - covers L2 resources including computation, tx payload, event emission, code + /// size, etc. Units: 1 Cairo step = 100 L2 gas. + /// + /// Pre 0.13.3. this field is signed but never used. + pub l2_gas: ResourceBounds, +} + /// Transaction resource bounds. /// /// ## NOTE @@ -40,7 +49,6 @@ pub struct AllResourceBoundsMapping { /// For further details, refer to [Starknet v0.13.4 pre-release notes](https://community.starknet.io/t/starknet-v0-13-4-pre-release-notes/115257). #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ResourceBoundsMapping { /// Legacy bounds; only L1 gas bounds specified (backward compatibility). /// @@ -49,7 +57,7 @@ pub enum ResourceBoundsMapping { /// ommitted from this variant and is assumed to be zero during transaction hash computation. /// /// Supported in Starknet v0.13.4 but rejected in v0.14.0+. - L1Gas(ResourceBounds), + L1Gas(L1GasResourceBoundsMapping), /// All three resource bounds specified: L1 gas, L2 gas, and L1 data gas. /// @@ -88,6 +96,7 @@ pub struct FeeInfo { /// Units in which the fee is given pub unit: PriceUnit, } + /// Transaction tip amount. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] @@ -149,6 +158,252 @@ impl<'de> serde::Deserialize<'de> for Tip { } } +#[cfg(feature = "serde")] +impl serde::Serialize for ResourceBounds { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::SerializeStruct; + + if serializer.is_human_readable() { + let mut state = serializer.serialize_struct("ResourceBounds", 2)?; + state.serialize_field("max_amount", &format!("{:#x}", self.max_amount))?; + state.serialize_field( + "max_price_per_unit", + &format!("{:#x}", self.max_price_per_unit), + )?; + state.end() + } else { + let mut state = serializer.serialize_struct("ResourceBounds", 2)?; + state.serialize_field("max_amount", &self.max_amount)?; + state.serialize_field("max_price_per_unit", &self.max_price_per_unit)?; + state.end() + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for ResourceBounds { + fn deserialize>(deserializer: D) -> Result { + use std::fmt; + + use serde::de::{self, MapAccess, Visitor}; + use serde_json::Value; + + if deserializer.is_human_readable() { + struct __Visitor; + + impl<'de> Visitor<'de> for __Visitor { + type Value = ResourceBounds; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("struct ResourceBounds") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut max_amount = None; + let mut max_price_per_unit = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "max_amount" => { + if max_amount.is_some() { + return Err(de::Error::duplicate_field("max_amount")); + } + let value: Value = map.next_value()?; + max_amount = Some(match value { + Value::String(s) => { + if let Some(hex) = s.strip_prefix("0x") { + u64::from_str_radix(hex, 16) + .map_err(de::Error::custom)? + } else { + s.parse().map_err(de::Error::custom)? + } + } + Value::Number(n) => n + .as_u64() + .ok_or_else(|| de::Error::custom("invalid u64"))?, + _ => { + return Err(de::Error::custom( + "expected 0x-prefix hex string or number for \ + max_amount", + )) + } + }); + } + + "max_price_per_unit" => { + if max_price_per_unit.is_some() { + return Err(de::Error::duplicate_field("max_price_per_unit")); + } + + let value: Value = map.next_value()?; + max_price_per_unit = Some(match value { + Value::String(s) => { + if let Some(hex) = s.strip_prefix("0x") { + u128::from_str_radix(hex, 16) + .map_err(de::Error::custom)? + } else { + s.parse().map_err(de::Error::custom)? + } + } + Value::Number(n) => { + if let Some(u) = n.as_u64() { + u as u128 + } else { + return Err(de::Error::custom("invalid u128")); + } + } + _ => { + return Err(de::Error::custom( + "expected 0x-prefix hex string or number for \ + max_price_per_unit", + )) + } + }); + } + + _ => { + let _ = map.next_value::()?; + } + } + } + + let max_amount = + max_amount.ok_or_else(|| de::Error::missing_field("max_amount"))?; + let max_price_per_unit = max_price_per_unit + .ok_or_else(|| de::Error::missing_field("max_price_per_unit"))?; + + Ok(ResourceBounds { max_amount, max_price_per_unit }) + } + } + + deserializer.deserialize_struct( + "ResourceBounds", + &["max_amount", "max_price_per_unit"], + __Visitor, + ) + } else { + #[derive(serde::Deserialize)] + struct ResourceBoundsBinary { + max_amount: u64, + max_price_per_unit: u128, + } + + let binary = ResourceBoundsBinary::deserialize(deserializer)?; + Ok(ResourceBounds { + max_amount: binary.max_amount, + max_price_per_unit: binary.max_price_per_unit, + }) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for ResourceBoundsMapping { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::SerializeStruct; + + // For human readable formats (primarily targetting JSON), serialize as a flat object. + if serializer.is_human_readable() { + match self { + ResourceBoundsMapping::L1Gas(bounds) => { + let mut state = serializer.serialize_struct("ResourceBoundsMapping", 2)?; + state.serialize_field("l2_gas", &bounds.l2_gas)?; + state.serialize_field("l1_gas", &bounds.l1_gas)?; + state.end() + } + ResourceBoundsMapping::All(bounds) => { + let mut state = serializer.serialize_struct("ResourceBoundsMapping", 3)?; + state.serialize_field("l2_gas", &bounds.l2_gas)?; + state.serialize_field("l1_gas", &bounds.l1_gas)?; + state.serialize_field("l1_data_gas", &bounds.l1_data_gas)?; + state.end() + } + } + } + // For binary formats, use explicit enum tagging: + // + // * ResourceBoundsMapping::L1Gas = 0 + // * ResourceBoundsMapping::All = 1 + else { + match self { + ResourceBoundsMapping::L1Gas(v) => { + serializer.serialize_newtype_variant("ResourceBoundsMapping", 0, "L1Gas", v) + } + ResourceBoundsMapping::All(v) => { + serializer.serialize_newtype_variant("ResourceBoundsMapping", 1, "All", v) + } + } + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for ResourceBoundsMapping { + fn deserialize>(deserializer: D) -> Result { + use std::fmt; + + use serde::de::{self, EnumAccess, VariantAccess, Visitor}; + + if deserializer.is_human_readable() { + // For JSON: deserialize from unified object format + #[derive(serde::Deserialize)] + struct UnifiedResourceBounds { + l1_gas: ResourceBounds, + l2_gas: ResourceBounds, + l1_data_gas: Option, + } + + let unified = UnifiedResourceBounds::deserialize(deserializer)?; + + // If l1_data_gas is present, it's the All variant + if let Some(l1_data_gas) = unified.l1_data_gas { + Ok(ResourceBoundsMapping::All(AllResourceBoundsMapping { + l1_gas: unified.l1_gas, + l2_gas: unified.l2_gas, + l1_data_gas, + })) + } else { + // Otherwise it's the L1Gas variant + Ok(ResourceBoundsMapping::L1Gas(L1GasResourceBoundsMapping { + l1_gas: unified.l1_gas, + l2_gas: unified.l2_gas, + })) + } + } + // For binary formats, use standard enum deserialization (when derived using + // #[derive(Deserialize)]) + else { + struct __Visitor; + + impl<'de> Visitor<'de> for __Visitor { + type Value = ResourceBoundsMapping; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("ResourceBoundsMapping enum") + } + + fn visit_enum>(self, data: A) -> Result { + let (variant_idx, variant) = data.variant::()?; + + match variant_idx { + 0 => { + let value = variant.newtype_variant::()?; + Ok(ResourceBoundsMapping::L1Gas(value)) + } + 1 => { + let value = variant.newtype_variant::()?; + Ok(ResourceBoundsMapping::All(value)) + } + _ => Err(de::Error::custom("invalid variant index; expected 0 or 1")), + } + } + } + + deserializer.deserialize_enum("ResourceBoundsMapping", &["L1Gas", "All"], __Visitor) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -192,4 +447,120 @@ mod tests { let deserialized: Tip = serde_json::from_str(&serialized).unwrap(); assert_eq!(original, deserialized); } + + #[cfg(feature = "serde")] + #[test] + fn resource_bounds_mapping_json_serde() { + use serde_json::json; + + // ------------------------------------------- + // Legacy resource bounds mapping + + let json = json!({ + "l2_gas": { + "max_amount": "0x7d0", + "max_price_per_unit": "0xc8" + }, + "l1_gas": { + "max_amount": "0x3e8", + "max_price_per_unit": "0x64" + } + }); + + let bounds = ResourceBoundsMapping::L1Gas(L1GasResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 1000, max_price_per_unit: 100 }, + l2_gas: ResourceBounds { max_amount: 2000, max_price_per_unit: 200 }, + }); + + let serialized = serde_json::to_value(&bounds).unwrap(); + similar_asserts::assert_eq!(json, serialized); + + let deserialized: ResourceBoundsMapping = serde_json::from_value(json).unwrap(); + assert_eq!(deserialized, bounds); + + // ------------------------------------------- + // All resource bounds mapping + + let json = json!({ + "l2_gas": { + "max_amount": "0x7d0", + "max_price_per_unit": "0xc8" + }, + "l1_gas": { + "max_amount": "0x3e8", + "max_price_per_unit": "0x64" + }, + "l1_data_gas": { + "max_amount": "0xbb8", + "max_price_per_unit": "0x12c" + } + }); + + let bounds = ResourceBoundsMapping::All(AllResourceBoundsMapping { + l2_gas: ResourceBounds { max_amount: 2000, max_price_per_unit: 200 }, + l1_gas: ResourceBounds { max_amount: 1000, max_price_per_unit: 100 }, + l1_data_gas: ResourceBounds { max_amount: 3000, max_price_per_unit: 300 }, + }); + + let serialized = serde_json::to_value(&bounds).unwrap(); + similar_asserts::assert_eq!(json, serialized); + + let deserialized: ResourceBoundsMapping = serde_json::from_value(json).unwrap(); + assert_eq!(deserialized, bounds); + } + + #[cfg(feature = "serde")] + #[test] + fn resource_bounds_mapping_binary_serde() { + // Test L1Gas variant binary serialization (using postcard) + let l1_gas_mapping = ResourceBoundsMapping::L1Gas(L1GasResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 1000, max_price_per_unit: 100 }, + l2_gas: ResourceBounds { max_amount: 2000, max_price_per_unit: 200 }, + }); + + let binary = postcard::to_stdvec(&l1_gas_mapping).unwrap(); + let deserialized: ResourceBoundsMapping = postcard::from_bytes(&binary).unwrap(); + assert_eq!(deserialized, l1_gas_mapping); + + // Test All variant binary serialization + let all_mapping = ResourceBoundsMapping::All(AllResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 1000, max_price_per_unit: 100 }, + l2_gas: ResourceBounds { max_amount: 2000, max_price_per_unit: 200 }, + l1_data_gas: ResourceBounds { max_amount: 3000, max_price_per_unit: 300 }, + }); + + let binary = postcard::to_stdvec(&all_mapping).unwrap(); + let deserialized: ResourceBoundsMapping = postcard::from_bytes(&binary).unwrap(); + assert_eq!(deserialized, all_mapping); + + // Ensure binary format is different from JSON (uses enum tags) + // Binary should be more compact than JSON + let json_size = serde_json::to_string(&all_mapping).unwrap().len(); + assert!(binary.len() < json_size); + } + + #[cfg(feature = "serde")] + #[test] + fn resource_bounds_mapping_cross_format() { + // Test that the same data structure can be serialized/deserialized + // in both JSON and binary formats independently + let mapping = ResourceBoundsMapping::All(AllResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 5000, max_price_per_unit: 500 }, + l2_gas: ResourceBounds { max_amount: 6000, max_price_per_unit: 600 }, + l1_data_gas: ResourceBounds { max_amount: 7000, max_price_per_unit: 700 }, + }); + + // Serialize to JSON, deserialize, and verify + let json = serde_json::to_string(&mapping).unwrap(); + let from_json: ResourceBoundsMapping = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json, mapping); + + // Serialize to binary, deserialize, and verify + let binary = postcard::to_stdvec(&mapping).unwrap(); + let from_binary: ResourceBoundsMapping = postcard::from_bytes(&binary).unwrap(); + assert_eq!(from_binary, mapping); + + // Verify that JSON and binary deserializations produce the same result + assert_eq!(from_json, from_binary); + } } diff --git a/crates/primitives/src/transaction.rs b/crates/primitives/src/transaction.rs index 7b67a8e50..30b082487 100644 --- a/crates/primitives/src/transaction.rs +++ b/crates/primitives/src/transaction.rs @@ -6,7 +6,7 @@ use crate::chain::ChainId; use crate::class::{ClassHash, CompiledClassHash, ContractClass}; use crate::contract::{ContractAddress, Nonce}; use crate::da::DataAvailabilityMode; -use crate::fee::{ResourceBounds, ResourceBoundsMapping}; +use crate::fee::ResourceBoundsMapping; use crate::utils::transaction::{ compute_declare_v0_tx_hash, compute_declare_v1_tx_hash, compute_declare_v2_tx_hash, compute_declare_v3_tx_hash, compute_deploy_account_v1_tx_hash, @@ -338,8 +338,8 @@ impl InvokeTx { Felt::from(tx.sender_address), &tx.calldata, tx.tip, - bounds, - &ResourceBounds::ZERO, + &bounds.l1_gas, + &bounds.l2_gas, None, &tx.paymaster_data, tx.chain_id.into(), @@ -533,8 +533,8 @@ impl DeclareTx { tx.class_hash, tx.compiled_class_hash, tx.tip, - bounds, - &ResourceBounds::ZERO, + &bounds.l1_gas, + &bounds.l2_gas, None, &tx.paymaster_data, tx.chain_id.into(), @@ -702,8 +702,8 @@ impl DeployAccountTx { tx.class_hash, tx.contract_address_salt, tx.tip, - bounds, - &ResourceBounds::ZERO, + &bounds.l1_gas, + &bounds.l2_gas, None, &tx.paymaster_data, tx.chain_id.into(), diff --git a/crates/rpc/rpc-types/src/broadcasted.rs b/crates/rpc/rpc-types/src/broadcasted.rs index 3ea917bec..412ff7128 100644 --- a/crates/rpc/rpc-types/src/broadcasted.rs +++ b/crates/rpc/rpc-types/src/broadcasted.rs @@ -6,17 +6,14 @@ use katana_primitives::class::{ }; use katana_primitives::contract::Nonce; use katana_primitives::da::DataAvailabilityMode; -use katana_primitives::fee::{ - AllResourceBoundsMapping, ResourceBounds, ResourceBoundsMapping, Tip, -}; +use katana_primitives::fee::{ResourceBoundsMapping, Tip}; use katana_primitives::transaction::{ DeclareTx, DeclareTxV3, DeclareTxWithClass, DeployAccountTx, DeployAccountTxV3, InvokeTx, InvokeTxV3, TxHash, TxType, }; use katana_primitives::utils::get_contract_address; use katana_primitives::{ContractAddress, Felt}; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -use serde_utils::{deserialize_u128, deserialize_u64, serialize_as_hex}; +use serde::{de, Deserialize, Deserializer, Serialize}; use crate::class::SierraClass; @@ -61,10 +58,6 @@ pub struct UntypedBroadcastedTx { pub signature: Vec, pub tip: Tip, pub paymaster_data: Vec, - #[serde( - serialize_with = "serialize_resource_bounds_mapping", - deserialize_with = "deserialize_resource_bounds_mapping" - )] pub resource_bounds: ResourceBoundsMapping, pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, @@ -601,109 +594,6 @@ impl<'de> Deserialize<'de> for BroadcastedDeployAccountTx { } } -#[derive(Debug, Serialize, Deserialize)] -#[serde(untagged)] -enum RpcResourceBoundsMapping { - Current { - #[serde( - serialize_with = "serialize_resource_bounds", - deserialize_with = "deserialize_resource_bounds" - )] - l1_gas: ResourceBounds, - - #[serde( - serialize_with = "serialize_resource_bounds", - deserialize_with = "deserialize_resource_bounds" - )] - l2_gas: ResourceBounds, - - #[serde( - serialize_with = "serialize_resource_bounds", - deserialize_with = "deserialize_resource_bounds" - )] - l1_data_gas: ResourceBounds, - }, - - Legacy { - #[serde( - serialize_with = "serialize_resource_bounds", - deserialize_with = "deserialize_resource_bounds" - )] - l1_gas: ResourceBounds, - - #[serde( - serialize_with = "serialize_resource_bounds", - deserialize_with = "deserialize_resource_bounds" - )] - l2_gas: ResourceBounds, - }, -} - -#[derive(Serialize, Deserialize)] -struct RpcResourceBounds { - #[serde(serialize_with = "serialize_as_hex", deserialize_with = "deserialize_u64")] - max_amount: u64, - #[serde(serialize_with = "serialize_as_hex", deserialize_with = "deserialize_u128")] - max_price_per_unit: u128, -} - -fn serialize_resource_bounds_mapping( - resource_bounds: &ResourceBoundsMapping, - serializer: S, -) -> Result { - let rpc_mapping = match resource_bounds { - ResourceBoundsMapping::L1Gas(l1_gas) => RpcResourceBoundsMapping::Legacy { - l1_gas: l1_gas.clone(), - l2_gas: ResourceBounds::default(), - }, - - ResourceBoundsMapping::All(AllResourceBoundsMapping { l1_gas, l2_gas, l1_data_gas }) => { - RpcResourceBoundsMapping::Current { - l1_gas: l1_gas.clone(), - l2_gas: l2_gas.clone(), - l1_data_gas: l1_data_gas.clone(), - } - } - }; - - rpc_mapping.serialize(serializer) -} - -fn deserialize_resource_bounds_mapping<'de, D: Deserializer<'de>>( - deserializer: D, -) -> Result { - let rpc_mapping = RpcResourceBoundsMapping::deserialize(deserializer)?; - - match rpc_mapping { - RpcResourceBoundsMapping::Legacy { l1_gas, .. } => Ok(ResourceBoundsMapping::L1Gas(l1_gas)), - RpcResourceBoundsMapping::Current { l1_gas, l2_gas, l1_data_gas } => { - Ok(ResourceBoundsMapping::All(AllResourceBoundsMapping { l1_gas, l2_gas, l1_data_gas })) - } - } -} - -fn serialize_resource_bounds( - resource_bounds: &ResourceBounds, - serializer: S, -) -> Result { - let helper = RpcResourceBounds { - max_amount: resource_bounds.max_amount, - max_price_per_unit: resource_bounds.max_price_per_unit, - }; - - helper.serialize(serializer) -} - -fn deserialize_resource_bounds<'de, D: Deserializer<'de>>( - deserializer: D, -) -> Result { - let helper = RpcResourceBounds::deserialize(deserializer)?; - Ok(ResourceBounds { - max_amount: helper.max_amount, - max_price_per_unit: helper.max_price_per_unit, - }) -} - #[cfg(test)] mod tests { use assert_matches::assert_matches; @@ -715,64 +605,9 @@ mod tests { use super::*; use crate::broadcasted::{ AddDeclareTransactionResponse, AddDeployAccountTransactionResponse, - AddInvokeTransactionResponse, BroadcastedTx, RpcResourceBoundsMapping, - UntypedBroadcastedTxError, + AddInvokeTransactionResponse, BroadcastedTx, UntypedBroadcastedTxError, }; - #[test] - fn legacy_rpc_resource_bounds_serde() { - let json = json!({ - "l1_gas": { - "max_amount": "0x1", - "max_price_per_unit": "0x1" - }, - "l2_gas": { - "max_amount": "0x0", - "max_price_per_unit": "0x0" - } - }); - - let resource_bounds: RpcResourceBoundsMapping = serde_json::from_value(json).unwrap(); - - assert_matches!(resource_bounds, RpcResourceBoundsMapping::Legacy { l1_gas, l2_gas } => { - assert_eq!(l1_gas.max_amount, 1); - assert_eq!(l1_gas.max_price_per_unit, 1); - assert_eq!(l2_gas.max_amount, 0); - assert_eq!(l2_gas.max_price_per_unit, 0); - }); - } - - #[test] - fn rpc_resource_bounds_serde() { - let json = json!({ - "l2_gas": { - "max_amount": "0xa", - "max_price_per_unit": "0xb" - }, - "l1_gas": { - "max_amount": "0x1", - "max_price_per_unit": "0x2" - }, - "l1_data_gas": { - "max_amount": "0xabc", - "max_price_per_unit": "0x1337" - } - }); - - let resource_bounds: RpcResourceBoundsMapping = serde_json::from_value(json).unwrap(); - - assert_matches!(resource_bounds, RpcResourceBoundsMapping::Current { l2_gas, l1_gas, l1_data_gas } => { - assert_eq!(l2_gas.max_amount, 0xa); - assert_eq!(l2_gas.max_price_per_unit, 0xb); - - assert_eq!(l1_gas.max_amount, 0x1); - assert_eq!(l1_gas.max_price_per_unit, 0x2); - - assert_eq!(l1_data_gas.max_amount, 0xabc); - assert_eq!(l1_data_gas.max_price_per_unit, 0x1337); - }); - } - #[test] fn untyped_invoke_tx_serde() { let json = json!({ @@ -940,8 +775,10 @@ mod tests { assert_eq!(untyped.fee_data_availability_mode, DataAvailabilityMode::L1); assert_eq!(untyped.nonce_data_availability_mode, DataAvailabilityMode::L1); assert_matches!(&untyped.resource_bounds, ResourceBoundsMapping::L1Gas(bounds) => { - assert_eq!(bounds.max_amount, 0x100); - assert_eq!(bounds.max_price_per_unit, 0x200); + assert_eq!(bounds.l1_gas.max_amount, 0x100); + assert_eq!(bounds.l1_gas.max_price_per_unit, 0x200); + assert_eq!(bounds.l2_gas.max_amount, 0x0); + assert_eq!(bounds.l2_gas.max_price_per_unit, 0x0); }); // Tx specific fields @@ -996,8 +833,8 @@ mod tests { "max_price_per_unit": "0x200" }, "l2_gas": { - "max_amount": "0x0", - "max_price_per_unit": "0x0" + "max_amount": "0x123", + "max_price_per_unit": "0x1337" } }, "nonce_data_availability_mode": "L1", @@ -1021,8 +858,10 @@ mod tests { assert_eq!(untyped.fee_data_availability_mode, DataAvailabilityMode::L1); assert_eq!(untyped.nonce_data_availability_mode, DataAvailabilityMode::L1); assert_matches!(&untyped.resource_bounds, ResourceBoundsMapping::L1Gas(bounds) => { - assert_eq!(bounds.max_amount, 0x100); - assert_eq!(bounds.max_price_per_unit, 0x200); + assert_eq!(bounds.l1_gas.max_amount, 0x100); + assert_eq!(bounds.l1_gas.max_price_per_unit, 0x200); + assert_eq!(bounds.l2_gas.max_amount, 0x123); + assert_eq!(bounds.l2_gas.max_price_per_unit, 0x1337); }); // Tx specific fields diff --git a/crates/rpc/rpc-types/src/transaction.rs b/crates/rpc/rpc-types/src/transaction.rs index 91d6ebde2..dfb9d5e1b 100644 --- a/crates/rpc/rpc-types/src/transaction.rs +++ b/crates/rpc/rpc-types/src/transaction.rs @@ -2,11 +2,10 @@ use katana_primitives::class::{ClassHash, CompiledClassHash}; use katana_primitives::contract::Nonce; use katana_primitives::da::DataAvailabilityMode; use katana_primitives::execution::EntryPointSelector; -use katana_primitives::fee::{AllResourceBoundsMapping, Tip}; +use katana_primitives::fee::{ResourceBoundsMapping, Tip}; use katana_primitives::transaction::TxHash; use katana_primitives::{transaction as primitives, ContractAddress, Felt}; use serde::{Deserialize, Serialize}; -use starknet::core::types::ResourceBoundsMapping; use crate::ExecutionResult; @@ -357,7 +356,7 @@ impl From for RpcTx { fee_data_availability_mode: tx.fee_data_availability_mode, nonce_data_availability_mode: tx.nonce_data_availability_mode, paymaster_data: tx.paymaster_data, - resource_bounds: to_rpc_resource_bounds(tx.resource_bounds), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), })), }, @@ -397,7 +396,7 @@ impl From for RpcTx { fee_data_availability_mode: tx.fee_data_availability_mode, nonce_data_availability_mode: tx.nonce_data_availability_mode, paymaster_data: tx.paymaster_data, - resource_bounds: to_rpc_resource_bounds(tx.resource_bounds), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), }), }), @@ -432,7 +431,7 @@ impl From for RpcTx { fee_data_availability_mode: tx.fee_data_availability_mode, nonce_data_availability_mode: tx.nonce_data_availability_mode, paymaster_data: tx.paymaster_data, - resource_bounds: to_rpc_resource_bounds(tx.resource_bounds), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), }) } @@ -448,89 +447,6 @@ impl From for RpcTx { } } -fn to_rpc_resource_bounds( - bounds: katana_primitives::fee::ResourceBoundsMapping, -) -> starknet::core::types::ResourceBoundsMapping { - match bounds { - katana_primitives::fee::ResourceBoundsMapping::All(all_bounds) => { - starknet::core::types::ResourceBoundsMapping { - l1_gas: starknet::core::types::ResourceBounds { - max_amount: all_bounds.l1_gas.max_amount, - max_price_per_unit: all_bounds.l1_gas.max_price_per_unit, - }, - l2_gas: starknet::core::types::ResourceBounds { - max_amount: all_bounds.l2_gas.max_amount, - max_price_per_unit: all_bounds.l2_gas.max_price_per_unit, - }, - l1_data_gas: starknet::core::types::ResourceBounds { - max_amount: all_bounds.l1_data_gas.max_amount, - max_price_per_unit: all_bounds.l1_data_gas.max_price_per_unit, - }, - } - } - // The `l1_data_gas` bounds should actually be ommitted but because `starknet-rs` doesn't - // support older RPC spec, we default to zero. This aren't technically accurate so should - // find an alternative or completely remove legacy support. But we need to support in order - // to maintain backward compatibility from older database version. - katana_primitives::fee::ResourceBoundsMapping::L1Gas(l1_gas_bounds) => { - starknet::core::types::ResourceBoundsMapping { - l1_gas: starknet::core::types::ResourceBounds { - max_amount: l1_gas_bounds.max_amount, - max_price_per_unit: l1_gas_bounds.max_price_per_unit, - }, - l2_gas: starknet::core::types::ResourceBounds { - max_amount: 0, - max_price_per_unit: 0, - }, - l1_data_gas: starknet::core::types::ResourceBounds { - max_amount: 0, - max_price_per_unit: 0, - }, - } - } - } -} - -fn from_rpc_resource_bounds( - bounds: starknet::core::types::ResourceBoundsMapping, -) -> katana_primitives::fee::ResourceBoundsMapping { - // If l2_gas and l1_data_gas are zero, treat it as L1Gas only (legacy support) - // - // this is technically an incorrect way to do this because the l2_gas and l1_data_gas can - // technically still be zero even if we're using non-legacy resource bounds (ie all bounds are - // set). the only way to do this is to use a different type/variant to represent a legacy - // resource bounds mapping. ideally we could just use the ResourceBoundsMapping from - // katana-primitives, but the L1Gas (ie legacy) has been incorrectly defined (lack of l2_gas - // field) and we can't simply add it because it'll break the type serialization format. - if bounds.l2_gas.max_amount == 0 - && bounds.l2_gas.max_price_per_unit == 0 - && bounds.l1_data_gas.max_amount == 0 - && bounds.l1_data_gas.max_price_per_unit == 0 - { - katana_primitives::fee::ResourceBoundsMapping::L1Gas( - katana_primitives::fee::ResourceBounds { - max_amount: bounds.l1_gas.max_amount, - max_price_per_unit: bounds.l1_gas.max_price_per_unit, - }, - ) - } else { - katana_primitives::fee::ResourceBoundsMapping::All(AllResourceBoundsMapping { - l1_gas: katana_primitives::fee::ResourceBounds { - max_amount: bounds.l1_gas.max_amount, - max_price_per_unit: bounds.l1_gas.max_price_per_unit, - }, - l2_gas: katana_primitives::fee::ResourceBounds { - max_amount: bounds.l2_gas.max_amount, - max_price_per_unit: bounds.l2_gas.max_price_per_unit, - }, - l1_data_gas: katana_primitives::fee::ResourceBounds { - max_amount: bounds.l1_data_gas.max_amount, - max_price_per_unit: bounds.l1_data_gas.max_price_per_unit, - }, - }) - } -} - impl From for primitives::TxWithHash { fn from(value: RpcTxWithHash) -> Self { primitives::TxWithHash { @@ -567,7 +483,7 @@ impl From for primitives::Tx { nonce: tx.nonce, calldata: tx.calldata, signature: tx.signature, - resource_bounds: from_rpc_resource_bounds(tx.resource_bounds), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), paymaster_data: tx.paymaster_data, account_deployment_data: tx.account_deployment_data, @@ -611,7 +527,7 @@ impl From for primitives::Tx { signature: tx.signature, class_hash: tx.class_hash, compiled_class_hash: tx.compiled_class_hash, - resource_bounds: from_rpc_resource_bounds(tx.resource_bounds), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), paymaster_data: tx.paymaster_data, account_deployment_data: tx.account_deployment_data, @@ -654,7 +570,7 @@ impl From for primitives::Tx { contract_address: Default::default(), contract_address_salt: tx.contract_address_salt, constructor_calldata: tx.constructor_calldata, - resource_bounds: from_rpc_resource_bounds(tx.resource_bounds), + resource_bounds: tx.resource_bounds, tip: tx.tip.into(), paymaster_data: tx.paymaster_data, nonce_data_availability_mode: tx.nonce_data_availability_mode, diff --git a/crates/rpc/rpc-types/tests/transaction.rs b/crates/rpc/rpc-types/tests/transaction.rs index 8f697181c..cda2b9c81 100644 --- a/crates/rpc/rpc-types/tests/transaction.rs +++ b/crates/rpc/rpc-types/tests/transaction.rs @@ -1,6 +1,9 @@ use assert_matches::assert_matches; use katana_primitives::da::DataAvailabilityMode; -use katana_primitives::fee::{ResourceBoundsMapping, Tip}; +use katana_primitives::fee::{ + AllResourceBoundsMapping, L1GasResourceBoundsMapping, ResourceBounds, ResourceBoundsMapping, + Tip, +}; use katana_primitives::{address, felt, transaction as primitives, ContractAddress}; use katana_rpc_types::transaction::{ RpcDeclareTx, RpcDeployAccountTx, RpcInvokeTx, RpcTx, RpcTxWithHash, @@ -10,7 +13,6 @@ use katana_rpc_types::{ RpcDeployAccountTxV3, RpcDeployTx, RpcInvokeTxV0, RpcInvokeTxV1, RpcInvokeTxV3, RpcL1HandlerTx, }; use serde_json::Value; -use starknet::core::types::ResourceBounds as RpcResourceBounds; mod fixtures; @@ -48,9 +50,11 @@ fn invoke_transaction() { assert_eq!(tx.account_deployment_data, vec![]); assert_eq!(tx.tip, Tip::new(0x5f5e100)); - assert_eq!(tx.resource_bounds.l1_data_gas, RpcResourceBounds { max_amount: 0x2710, max_price_per_unit: 0x8d79883d20000 }); - assert_eq!(tx.resource_bounds.l1_gas, RpcResourceBounds { max_amount: 0x249f0, max_price_per_unit: 0x8d79883d20000 }); - assert_eq!(tx.resource_bounds.l2_gas, RpcResourceBounds { max_amount: 0x5f5e100, max_price_per_unit: 0xba43b7400 }); + assert_matches!(&tx.resource_bounds, ResourceBoundsMapping::All(bounds) => { + assert_eq!(bounds.l1_data_gas, ResourceBounds { max_amount: 0x2710, max_price_per_unit: 0x8d79883d20000 }); + assert_eq!(bounds.l1_gas, ResourceBounds { max_amount: 0x249f0, max_price_per_unit: 0x8d79883d20000 }); + assert_eq!(bounds.l2_gas, ResourceBounds { max_amount: 0x5f5e100, max_price_per_unit: 0xba43b7400 }); + }); }); let serialized = serde_json::to_value(&tx).unwrap(); @@ -87,9 +91,12 @@ fn declare_transaction() { assert_eq!(tx.paymaster_data, vec![]); assert_eq!(tx.tip, Tip::new(0x0)); - assert_eq!(tx.resource_bounds.l1_data_gas, RpcResourceBounds { max_amount: 0x2710, max_price_per_unit: 0x8d79883d20000 }); - assert_eq!(tx.resource_bounds.l1_gas, RpcResourceBounds { max_amount: 0x249f0, max_price_per_unit: 0x8d79883d20000 }); - assert_eq!(tx.resource_bounds.l2_gas, RpcResourceBounds { max_amount: 0x6c76900, max_price_per_unit: 0xba43b7400 }); + assert_matches!(&tx.resource_bounds, ResourceBoundsMapping::All(bounds) => { + assert_eq!(bounds.l1_data_gas, ResourceBounds { max_amount: 0x2710, max_price_per_unit: 0x8d79883d20000 }); + assert_eq!(bounds.l1_gas, ResourceBounds { max_amount: 0x249f0, max_price_per_unit: 0x8d79883d20000 }); + assert_eq!(bounds.l2_gas, ResourceBounds { max_amount: 0x6c76900, max_price_per_unit: 0xba43b7400 }); + }); + }); let serialized = serde_json::to_value(&tx).unwrap(); @@ -128,9 +135,11 @@ fn deploy_account_transaction() { assert_eq!(tx.paymaster_data, vec![]); assert_eq!(tx.tip, Tip::new(0x5f5e100)); - assert_eq!(tx.resource_bounds.l1_data_gas, RpcResourceBounds { max_amount: 0x2710, max_price_per_unit: 0x8d79883d20000 }); - assert_eq!(tx.resource_bounds.l1_gas, RpcResourceBounds { max_amount: 0x249f0, max_price_per_unit: 0x8d79883d20000 }); - assert_eq!(tx.resource_bounds.l2_gas, RpcResourceBounds { max_amount: 0x5f5e100, max_price_per_unit: 0xba43b7400 }); + assert_matches!(&tx.resource_bounds, ResourceBoundsMapping::All(bounds) => { + assert_eq!(bounds.l1_data_gas, ResourceBounds { max_amount: 0x2710, max_price_per_unit: 0x8d79883d20000 }); + assert_eq!(bounds.l1_gas, ResourceBounds { max_amount: 0x249f0, max_price_per_unit: 0x8d79883d20000 }); + assert_eq!(bounds.l2_gas, ResourceBounds { max_amount: 0x5f5e100, max_price_per_unit: 0xba43b7400 }); + }); }); let serialized = serde_json::to_value(&tx).unwrap(); @@ -182,11 +191,11 @@ fn rpc_to_primitives_invoke_v3() { calldata: vec![felt!("0x1"), felt!("0x2"), felt!("0x3")], signature: vec![felt!("0xabc"), felt!("0xdef")], nonce: felt!("0x5"), - resource_bounds: starknet::core::types::ResourceBoundsMapping { - l1_gas: RpcResourceBounds { max_amount: 0x1000, max_price_per_unit: 0x100 }, - l2_gas: RpcResourceBounds { max_amount: 0x2000, max_price_per_unit: 0x200 }, - l1_data_gas: RpcResourceBounds { max_amount: 0x3000, max_price_per_unit: 0x300 }, - }, + resource_bounds: ResourceBoundsMapping::All(AllResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 0x1000, max_price_per_unit: 0x100 }, + l2_gas: ResourceBounds { max_amount: 0x2000, max_price_per_unit: 0x200 }, + l1_data_gas: ResourceBounds { max_amount: 0x3000, max_price_per_unit: 0x300 }, + }), tip: Tip::new(0x50), paymaster_data: vec![felt!("0x999")], account_deployment_data: vec![felt!("0x888"), felt!("0x777")], @@ -296,11 +305,11 @@ fn rpc_to_primitives_declare_v3() { signature: vec![felt!("0x444"), felt!("0x555")], nonce: felt!("0x20"), class_hash: felt!("0x666777"), - resource_bounds: starknet::core::types::ResourceBoundsMapping { - l1_gas: RpcResourceBounds { max_amount: 0x100, max_price_per_unit: 0x10 }, - l2_gas: RpcResourceBounds { max_amount: 0x200, max_price_per_unit: 0x20 }, - l1_data_gas: RpcResourceBounds { max_amount: 0x300, max_price_per_unit: 0x30 }, - }, + resource_bounds: ResourceBoundsMapping::All(AllResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 0x100, max_price_per_unit: 0x10 }, + l2_gas: ResourceBounds { max_amount: 0x200, max_price_per_unit: 0x20 }, + l1_data_gas: ResourceBounds { max_amount: 0x300, max_price_per_unit: 0x30 }, + }), tip: Tip::new(0x99), paymaster_data: vec![felt!("0xfff")], account_deployment_data: vec![felt!("0xeee")], @@ -441,11 +450,11 @@ fn rpc_to_primitives_deploy_account_v3() { contract_address_salt: felt!("0xccc"), constructor_calldata: vec![felt!("0xddd"), felt!("0xeee")], class_hash: felt!("0xfff111"), - resource_bounds: starknet::core::types::ResourceBoundsMapping { - l1_gas: RpcResourceBounds { max_amount: 0x400, max_price_per_unit: 0x40 }, - l2_gas: RpcResourceBounds { max_amount: 0x500, max_price_per_unit: 0x50 }, - l1_data_gas: RpcResourceBounds { max_amount: 0x600, max_price_per_unit: 0x60 }, - }, + resource_bounds: ResourceBoundsMapping::All(AllResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 0x400, max_price_per_unit: 0x40 }, + l2_gas: ResourceBounds { max_amount: 0x500, max_price_per_unit: 0x50 }, + l1_data_gas: ResourceBounds { max_amount: 0x600, max_price_per_unit: 0x60 }, + }), tip: Tip::new(0x88), paymaster_data: vec![felt!("0x222333")], nonce_data_availability_mode: DataAvailabilityMode::L1, @@ -575,7 +584,6 @@ fn rpc_to_primitives_deploy() { } #[test] -#[ignore = "we don't have proper support for legacy resource bounds on both RPC and primitives"] fn rpc_to_primitives_resource_bounds_l1_only() { // Test the case where only L1 gas bounds are set (legacy support) let rpc_tx = RpcTxWithHash { @@ -585,11 +593,10 @@ fn rpc_to_primitives_resource_bounds_l1_only() { calldata: vec![], signature: vec![], nonce: felt!("0x1"), - resource_bounds: starknet::core::types::ResourceBoundsMapping { - l1_gas: RpcResourceBounds { max_amount: 0x1000, max_price_per_unit: 0x100 }, - l2_gas: RpcResourceBounds { max_amount: 0, max_price_per_unit: 0 }, - l1_data_gas: RpcResourceBounds { max_amount: 0, max_price_per_unit: 0 }, - }, + resource_bounds: ResourceBoundsMapping::L1Gas(L1GasResourceBoundsMapping { + l1_gas: ResourceBounds { max_amount: 0x1000, max_price_per_unit: 0x100 }, + l2_gas: ResourceBounds { max_amount: 0x99, max_price_per_unit: 0x88 }, + }), tip: Tip::new(0), paymaster_data: vec![], account_deployment_data: vec![], @@ -603,8 +610,10 @@ fn rpc_to_primitives_resource_bounds_l1_only() { assert_matches!(&primitives_tx.transaction, primitives::Tx::Invoke(primitives::InvokeTx::V3(tx)) => { // When l2_gas and l1_data_gas are zero, it should be converted to L1Gas variant assert_matches!(&tx.resource_bounds, ResourceBoundsMapping::L1Gas(bounds) => { - assert_eq!(bounds.max_amount, 0x1000); - assert_eq!(bounds.max_price_per_unit, 0x100); + assert_eq!(bounds.l1_gas.max_amount, 0x1000); + assert_eq!(bounds.l1_gas.max_price_per_unit, 0x100); + assert_eq!(bounds.l2_gas.max_amount, 0x99); + assert_eq!(bounds.l2_gas.max_price_per_unit, 0x88); }); }); diff --git a/crates/storage/db/src/models/versioned/transaction/v6.rs b/crates/storage/db/src/models/versioned/transaction/v6.rs index f615cb16a..814c7025a 100644 --- a/crates/storage/db/src/models/versioned/transaction/v6.rs +++ b/crates/storage/db/src/models/versioned/transaction/v6.rs @@ -2,15 +2,25 @@ //! has been defined in database version 6. Modifying the order will break compatibility with the //! version. -use katana_primitives::{chain, class, contract, da, fee, transaction, Felt}; +use katana_primitives::fee::{self}; +use katana_primitives::{chain, class, contract, da, transaction, Felt}; use serde::{Deserialize, Serialize}; +#[repr(u8)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(test, derive(::arbitrary::Arbitrary))] +pub enum Tx { + Invoke(InvokeTx) = 0, + Declare(DeclareTx), + L1Handler(transaction::L1HandlerTx), + DeployAccount(DeployAccountTx), + Deploy(transaction::DeployTx), +} + #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] #[cfg_attr(test, derive(::arbitrary::Arbitrary))] pub struct ResourceBoundsMapping { - #[serde(alias = "L1_GAS")] pub l1_gas: fee::ResourceBounds, - #[serde(alias = "L2_GAS")] pub l2_gas: fee::ResourceBounds, } @@ -91,54 +101,9 @@ pub enum DeployAccountTx { V3(DeployAccountTxV3), } -#[repr(u8)] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(test, derive(::arbitrary::Arbitrary))] -pub enum Tx { - Invoke(InvokeTx) = 0, - Declare(DeclareTx), - L1Handler(transaction::L1HandlerTx), - DeployAccount(DeployAccountTx), - Deploy(transaction::DeployTx), -} - -impl Tx { - pub fn version(&self) -> Felt { - match self { - Tx::Invoke(tx) => match tx { - InvokeTx::V0(_) => Felt::ZERO, - InvokeTx::V1(_) => Felt::ONE, - InvokeTx::V3(_) => Felt::THREE, - }, - Tx::Declare(tx) => match tx { - DeclareTx::V0(_) => Felt::ZERO, - DeclareTx::V1(_) => Felt::ONE, - DeclareTx::V2(_) => Felt::TWO, - DeclareTx::V3(_) => Felt::THREE, - }, - Tx::L1Handler(tx) => tx.version, - Tx::DeployAccount(tx) => match tx { - DeployAccountTx::V1(_) => Felt::ONE, - DeployAccountTx::V3(_) => Felt::THREE, - }, - Tx::Deploy(tx) => tx.version, - } - } - - pub fn r#type(&self) -> transaction::TxType { - match self { - Self::Invoke(_) => transaction::TxType::Invoke, - Self::Deploy(_) => transaction::TxType::Deploy, - Self::Declare(_) => transaction::TxType::Declare, - Self::L1Handler(_) => transaction::TxType::L1Handler, - Self::DeployAccount(_) => transaction::TxType::DeployAccount, - } - } -} - impl From for fee::ResourceBoundsMapping { fn from(v6: ResourceBoundsMapping) -> Self { - Self::L1Gas(v6.l1_gas) + Self::L1Gas(fee::L1GasResourceBoundsMapping { l1_gas: v6.l1_gas, l2_gas: v6.l2_gas }) } } @@ -272,8 +237,10 @@ mod tests { match converted { fee::ResourceBoundsMapping::L1Gas(bounds) => { - assert_eq!(bounds.max_amount, 1000); - assert_eq!(bounds.max_price_per_unit, 100); + assert_eq!(bounds.l1_gas.max_amount, 1000); + assert_eq!(bounds.l1_gas.max_price_per_unit, 100); + assert_eq!(bounds.l2_gas.max_amount, 2000); + assert_eq!(bounds.l2_gas.max_price_per_unit, 200); } fee::ResourceBoundsMapping::All(..) => panic!("wrong variant"), } @@ -306,8 +273,10 @@ mod tests { match converted.resource_bounds { fee::ResourceBoundsMapping::L1Gas(bounds) => { - assert_eq!(bounds.max_amount, 1000); - assert_eq!(bounds.max_price_per_unit, 100); + assert_eq!(bounds.l1_gas.max_amount, 1000); + assert_eq!(bounds.l1_gas.max_price_per_unit, 100); + assert_eq!(bounds.l2_gas.max_amount, 2000); + assert_eq!(bounds.l2_gas.max_price_per_unit, 200); } fee::ResourceBoundsMapping::All(..) => panic!("wrong variant"), }