diff --git a/.github/workflows/parachain.yml b/.github/workflows/parachain.yml new file mode 100644 index 000000000000..0df1fe3872a0 --- /dev/null +++ b/.github/workflows/parachain.yml @@ -0,0 +1,356 @@ +name: bridge + +on: + push: + paths: + - "bridges/snowbridge/**" + - "!bridges/snowbridge/README.md" + - "!bridges/snowbridge/LICENSE" + branches: + - snowbridge + pull_request: + workflow_dispatch: + +env: + FUZZ_MAX_LEN: 10000000000 + FUZZ_MAX_RUNS: 30000 + RUST_NIGHTLY: "2024-02-08" + +jobs: + format: + runs-on: snowbridge-runner + env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + RUSTFLAGS: -C debuginfo=1 + SKIP_WASM_BUILD: 1 + steps: + - uses: actions/checkout@v2 + with: + submodules: "true" + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/cache@v1 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + - uses: actions/setup-node@v4.0.0 + with: + node-version: "18.x" + registry-url: "https://npm.pkg.github.com" + scope: "@paritytech" + - name: setup rust toolchain + run: | + rustup target add wasm32-unknown-unknown + curl -LO https://github.com/paritytech/rustc-rv32e-toolchain/releases/download/v1.1.0/rust-rve-nightly-2024-01-05-x86_64-unknown-linux-gnu.tar.zst + tar -I zstd -xf rust-rve-nightly-2024-01-05-x86_64-unknown-linux-gnu.tar.zst + mv rve-nightly ~/.rustup/toolchains/ + rustup toolchain install nightly + rustup component add rustfmt --toolchain nightly + rustup show + - name: format + run: > + cargo +nightly fmt + -p snowbridge-pallet-ethereum-client + -p snowbridge-pallet-inbound-queue + -p snowbridge-pallet-outbound-queue + -p snowbridge-outbound-queue-merkle-tree + -p snowbridge-pallet-system + -p snowbridge-beacon-primitives + -p snowbridge-core + -p snowbridge-ethereum + -p snowbridge-router-primitives + -p snowbridge-runtime-common + -p snowbridge-runtime-test-common + -p bridge-hub-rococo-runtime + -p bridge-hub-rococo-integration-tests + -p asset-hub-rococo-integration-tests + -p bridge-hub-westend-runtime + -p bridge-hub-westend-integration-tests + -p asset-hub-westend-integration-tests + -- + --check + - name: install taplo + run: | + cargo install taplo-cli --locked + - name: taplo + run: taplo format --check --config .config/taplo.toml + - name: install zepter + run: | + cargo install zepter -f --locked + - name: zepter + run: zepter run check + - name: lint-markdown + run: | + npm install -g markdownlint-cli + markdownlint --version + markdownlint --config .github/.markdownlint.yaml --ignore target + + check: + runs-on: snowbridge-runner + env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + RUSTFLAGS: -C debuginfo=1 + SKIP_WASM_BUILD: 1 + steps: + - uses: actions/checkout@v2 + with: + submodules: "true" + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/cache@v1 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + - uses: actions/setup-node@v4.0.0 + with: + node-version: "18.x" + registry-url: "https://npm.pkg.github.com" + scope: "@paritytech" + - name: setup rust toolchain + run: | + rustup target add wasm32-unknown-unknown + curl -LO https://github.com/paritytech/rustc-rv32e-toolchain/releases/download/v1.1.0/rust-rve-nightly-2024-01-05-x86_64-unknown-linux-gnu.tar.zst + tar -I zstd -xf rust-rve-nightly-2024-01-05-x86_64-unknown-linux-gnu.tar.zst + mv rve-nightly ~/.rustup/toolchains/ + rustup toolchain install nightly + rustup component add rustfmt --toolchain nightly + rustup show + - name: cargo check + run: cargo check --workspace --all-features + - name: clippy + run: cargo clippy --all-features -- -D warnings + + test: + needs: check + runs-on: snowbridge-runner + env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + RUSTFLAGS: -C debuginfo=1 + SKIP_WASM_BUILD: 1 + RUST_MIN_STACK: 8388608 + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + submodules: "true" + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/cache@v1 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + - name: setup rust toolchain + run: rustup show + # Increase stack limit for beacon light client tests + - run: sudo prlimit --pid $$ --stack=32768 + # Run tests for runtime-benchmarks feature + - name: Tests for runtime-benchmarks + run: > + cargo test + -p snowbridge-pallet-ethereum-client + -p snowbridge-pallet-inbound-queue + -p snowbridge-pallet-outbound-queue + -p snowbridge-outbound-queue-merkle-tree + -p snowbridge-pallet-system + -p snowbridge-beacon-primitives + -p snowbridge-core + -p snowbridge-ethereum + -p snowbridge-router-primitives + -p snowbridge-runtime-common + --features runtime-benchmarks + # Run tests for all features + - name: Tests for all features + run: > + cargo test + -p snowbridge-pallet-ethereum-client + -p snowbridge-pallet-inbound-queue + -p snowbridge-pallet-outbound-queue + -p snowbridge-outbound-queue-merkle-tree + -p snowbridge-pallet-system + -p snowbridge-beacon-primitives + -p snowbridge-core + -p snowbridge-ethereum + -p snowbridge-router-primitives + -p snowbridge-runtime-common + --all-features + + coverage: + if: false + needs: check + runs-on: snowbridge-runner + env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + RUSTFLAGS: -C debuginfo=1 + SKIP_WASM_BUILD: 1 + RUST_MIN_STACK: 8388608 + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + submodules: "true" + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: setup rust toolchain + run: rustup show + - name: run coverage test + run: > + cargo install cargo-tarpaulin@0.27.0 && + cargo tarpaulin + --workspace + --engine llvm + --out xml + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v3 + with: + files: cobertura.xml + flags: rust + + check-cumulus: + runs-on: snowbridge-runner + steps: + - uses: actions/checkout@v2 + with: + submodules: "true" + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: setup rust toolchain + run: rustup show + - name: check bridge-hub fast-runtime + run: > + cargo check + --release --verbose + -p bridge-hub-rococo-runtime + -p bridge-hub-westend-runtime + --features fast-runtime + - name: check bridge-hub runtime-benchmarks + run: > + cargo check + --release --verbose + -p bridge-hub-rococo-runtime + -p bridge-hub-westend-runtime + --features runtime-benchmarks + - name: check bridge-hub try-runtime + run: > + cargo check + --release --verbose + -p bridge-hub-rococo-runtime + -p bridge-hub-westend-runtime + --features try-runtime + - name: check bridge-hub all features + run: > + cargo check + --release --verbose + -p bridge-hub-rococo-runtime + -p bridge-hub-westend-runtime + --all-features + - name: check asset-hub all features + run: > + cargo check + --release --verbose + -p asset-hub-rococo-runtime + -p asset-hub-westend-runtime + --all-features + + runtime-tests: + needs: check + runs-on: snowbridge-runner + steps: + - uses: actions/checkout@v2 + with: + submodules: "true" + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: setup rust toolchain + run: rustup show + - name: snowbridge runtime tests + run: > + RUST_LOG=xcm=trace cargo test + -p bridge-hub-rococo-runtime + -p bridge-hub-westend-runtime + --test snowbridge + -- --nocapture + + integration-tests: + needs: check + runs-on: snowbridge-runner + steps: + - uses: actions/checkout@v2 + with: + submodules: "true" + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: setup rust toolchain + run: rustup show + - name: bridge-hub and asset-hub integration tests + run: > + RUST_LOG=xcm=trace cargo test + -p bridge-hub-rococo-integration-tests + -p asset-hub-rococo-integration-tests + -p bridge-hub-westend-integration-tests + -p asset-hub-westend-integration-tests + -- --nocapture + + beacon-fuzz: + if: false + needs: test + runs-on: snowbridge-runner + env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + RUSTFLAGS: -C debuginfo=1 + SKIP_WASM_BUILD: 1 + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + submodules: "true" + - uses: actions/cache@v1 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + - name: install nightly + run: rustup install --profile minimal nightly-$RUST_NIGHTLY + - name: Install cargo-fuzz from crates.io + uses: baptiste0928/cargo-install@v2 + with: + crate: cargo-fuzz + version: "^0.11.2" + - name: Fuzz force checkpoint extrinsic + run: > + cd bridges/snowbridge/pallets/ethereum-beacon-client && cargo +nightly-$RUST_NIGHTLY fuzz run fuzz_force_checkpoint -- + -max_len=$FUZZ_MAX_LEN -runs=$FUZZ_MAX_RUNS + - name: Fuzz submit extrinsic + run: > + cd bridges/snowbridge/pallets/ethereum-beacon-client && cargo +nightly-$RUST_NIGHTLY fuzz run fuzz_submit -- + -max_len=$FUZZ_MAX_LEN -runs=$FUZZ_MAX_RUNS + - name: Fuzz submit execution header extrinsic + run: > + cd bridges/snowbridge/pallets/ethereum-beacon-client && cargo +nightly-$RUST_NIGHTLY fuzz run fuzz_submit_execution_header -- + -max_len=$FUZZ_MAX_LEN -runs=$FUZZ_MAX_RUNS diff --git a/bridges/snowbridge/pallets/ethereum-client/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs index 6894977c21f4..526313f8236e 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/lib.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs @@ -33,7 +33,10 @@ mod tests; mod benchmarking; use frame_support::{ - dispatch::DispatchResult, pallet_prelude::OptionQuery, traits::Get, transactional, + dispatch::{DispatchResult, PostDispatchInfo}, + pallet_prelude::OptionQuery, + traits::Get, + transactional, }; use frame_system::ensure_signed; use snowbridge_beacon_primitives::{ @@ -82,6 +85,9 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; #[pallet::constant] type ForkVersions: Get; + /// Minimum gap between finalized headers for an update to be free. + #[pallet::constant] + type FreeHeadersInterval: Get; type WeightInfo: WeightInfo; } @@ -204,11 +210,10 @@ pub mod pallet { #[transactional] /// Submits a new finalized beacon header update. The update may contain the next /// sync committee. - pub fn submit(origin: OriginFor, update: Box) -> DispatchResult { + pub fn submit(origin: OriginFor, update: Box) -> DispatchResultWithPostInfo { ensure_signed(origin)?; ensure!(!Self::operating_mode().is_halted(), Error::::Halted); - Self::process_update(&update)?; - Ok(()) + Self::process_update(&update) } /// Halt or resume all pallet operations. May only be called by root. @@ -280,10 +285,9 @@ pub mod pallet { Ok(()) } - pub(crate) fn process_update(update: &Update) -> DispatchResult { + pub(crate) fn process_update(update: &Update) -> DispatchResultWithPostInfo { Self::verify_update(update)?; - Self::apply_update(update)?; - Ok(()) + Self::apply_update(update) } /// References and strictly follows @@ -432,8 +436,9 @@ pub mod pallet { /// Reference and strictly follows DispatchResult { + /// SyncCommitteePrepared type. Stores the provided finalized header. Updates are free + /// if the certain conditions are met, least of which being a successful update. + fn apply_update(update: &Update) -> DispatchResultWithPostInfo { let latest_finalized_state = FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) .ok_or(Error::::NotBootstrapped)?; @@ -465,11 +470,17 @@ pub mod pallet { }); }; + let pays_fee = Self::check_refundable(update, latest_finalized_state.slot); + let actual_weight = match update.next_sync_committee_update { + None => T::WeightInfo::submit(), + Some(_) => T::WeightInfo::submit_with_sync_committee(), + }; + if update.finalized_header.slot > latest_finalized_state.slot { Self::store_finalized_header(update.finalized_header, update.block_roots_root)?; } - Ok(()) + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } /// Computes the signing root for a given beacon header and domain. The hash tree root @@ -634,11 +645,29 @@ pub mod pallet { config::SLOTS_PER_EPOCH as u64, )); let domain_type = config::DOMAIN_SYNC_COMMITTEE.to_vec(); - // Domains are used for for seeds, for signatures, and for selecting aggregators. + // Domains are used for seeds, for signatures, and for selecting aggregators. let domain = Self::compute_domain(domain_type, fork_version, validators_root)?; // Hash tree root of SigningData - object root + domain let signing_root = Self::compute_signing_root(header, domain)?; Ok(signing_root) } + + /// Updates are free if the update is successful and the interval between the latest + /// finalized header in storage and the newly imported header is large enough. All + /// successful sync committee updates are free. + pub(super) fn check_refundable(update: &Update, latest_slot: u64) -> Pays { + // If the sync committee was successfully updated, the update may be free. + if update.next_sync_committee_update.is_some() { + return Pays::No; + } + + // If free headers are allowed and the latest finalized header is larger than the + // minimum slot interval, the header import transaction is free. + if update.finalized_header.slot >= latest_slot + T::FreeHeadersInterval::get() as u64 { + return Pays::No; + } + + Pays::Yes + } } } diff --git a/bridges/snowbridge/pallets/ethereum-client/src/mock.rs b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs index 96298d4fa896..b90e28c51f78 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/mock.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs @@ -111,6 +111,7 @@ parameter_types! { impl ethereum_beacon_client::Config for Test { type RuntimeEvent = RuntimeEvent; type ForkVersions = ChainForkVersions; + type FreeHeadersInterval = ConstU32<96>; type WeightInfo = (); } diff --git a/bridges/snowbridge/pallets/ethereum-client/src/tests.rs b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs index c16743b75ea4..200d536018a5 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/tests.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs @@ -1,21 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork use crate::{ - functions::compute_period, sync_committee_sum, verify_merkle_branch, BeaconHeader, - CompactBeaconState, Error, FinalizedBeaconState, LatestFinalizedBlockRoot, NextSyncCommittee, - SyncCommitteePrepared, -}; - -use crate::mock::{ - get_message_verification_payload, load_checkpoint_update_fixture, - load_finalized_header_update_fixture, load_next_finalized_header_update_fixture, - load_next_sync_committee_update_fixture, load_sync_committee_update_fixture, + functions::compute_period, + mock::{ + get_message_verification_payload, load_checkpoint_update_fixture, + load_finalized_header_update_fixture, load_next_finalized_header_update_fixture, + load_next_sync_committee_update_fixture, load_sync_committee_update_fixture, + }, + sync_committee_sum, verify_merkle_branch, BeaconHeader, CompactBeaconState, Error, + FinalizedBeaconState, LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared, }; pub use crate::mock::*; use crate::config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT}; -use frame_support::{assert_err, assert_noop, assert_ok}; +use frame_support::{assert_err, assert_noop, assert_ok, pallet_prelude::Pays}; use hex_literal::hex; use snowbridge_beacon_primitives::{ types::deneb, Fork, ForkVersions, NextSyncCommitteeUpdate, VersionedExecutionPayloadHeader, @@ -129,6 +128,23 @@ pub fn compute_domain_bls() { }); } +#[test] +pub fn may_refund_call_fee() { + let finalized_update = Box::new(load_next_finalized_header_update_fixture()); + let sync_committee_update = Box::new(load_sync_committee_update_fixture()); + new_tester().execute_with(|| { + // Not free, smaller than the allowed free header interval + assert_eq!( + EthereumBeaconClient::check_refundable(&finalized_update.clone(), 8190), + Pays::Yes + ); + // Is free, larger than the minimum interval + assert_eq!(EthereumBeaconClient::check_refundable(&finalized_update, 8000), Pays::No); + // Is free, valid sync committee update + assert_eq!(EthereumBeaconClient::check_refundable(&sync_committee_update, 8190), Pays::No); + }); +} + #[test] pub fn verify_merkle_branch_for_finalized_root() { new_tester().execute_with(|| { @@ -340,7 +356,9 @@ fn submit_update_in_current_period() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone())); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); let block_root: H256 = update.finalized_header.hash_tree_root().unwrap(); assert!(>::contains_key(block_root)); }); @@ -357,7 +375,9 @@ fn submit_update_with_sync_committee_in_current_period() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); assert!(!>::exists()); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update)); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); assert!(>::exists()); }); } @@ -374,20 +394,21 @@ fn reject_submit_update_in_next_period() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit( - RuntimeOrigin::signed(1), - sync_committee_update.clone() - )); + let result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); + // check an update in the next period is rejected - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()), - Error::::SyncCommitteeUpdateRequired - ); + let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()); + assert_err!(second_result, Error::::SyncCommitteeUpdateRequired); + assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); + // submit update with next sync committee - assert_ok!(EthereumBeaconClient::submit( - RuntimeOrigin::signed(1), - next_sync_committee_update - )); + let third_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_sync_committee_update); + assert_ok!(third_result); + assert_eq!(third_result.unwrap().pays_fee, Pays::No); // check same header in the next period can now be submitted successfully assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone())); let block_root: H256 = update.finalized_header.clone().hash_tree_root().unwrap(); @@ -407,10 +428,9 @@ fn submit_update_with_invalid_header_proof() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); assert!(!>::exists()); - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), - Error::::InvalidHeaderMerkleProof - ); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_err!(result, Error::::InvalidHeaderMerkleProof); + assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -426,10 +446,9 @@ fn submit_update_with_invalid_block_roots_proof() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); assert!(!>::exists()); - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), - Error::::InvalidBlockRootsRootMerkleProof - ); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_err!(result, Error::::InvalidBlockRootsRootMerkleProof); + assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -447,10 +466,9 @@ fn submit_update_with_invalid_next_sync_committee_proof() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); assert!(!>::exists()); - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), - Error::::InvalidSyncCommitteeMerkleProof - ); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_err!(result, Error::::InvalidSyncCommitteeMerkleProof); + assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -464,14 +482,14 @@ fn submit_update_with_skipped_period() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit( - RuntimeOrigin::signed(1), - sync_committee_update.clone() - )); - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), - Error::::SkippedSyncCommitteePeriod - ); + let result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_err!(second_result, Error::::SkippedSyncCommitteePeriod); + assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -487,9 +505,16 @@ fn submit_update_with_sync_committee_in_next_period() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); assert!(!>::exists()); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone())); + + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); assert!(>::exists()); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone())); + + let second_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone()); + assert_ok!(second_result); + assert_eq!(second_result.unwrap().pays_fee, Pays::No); let last_finalized_state = FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()).unwrap(); let last_synced_period = compute_period(last_finalized_state.slot); @@ -505,13 +530,12 @@ fn submit_update_with_sync_committee_invalid_signature_slot() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - // makes a invalid update with signature_slot should be more than attested_slot + // makes an invalid update with signature_slot should be more than attested_slot update.signature_slot = update.attested_header.slot; - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), - Error::::InvalidUpdateSlot - ); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_err!(result, Error::::InvalidUpdateSlot); + assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -525,10 +549,9 @@ fn submit_update_with_skipped_sync_committee_period() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_update), - Error::::SkippedSyncCommitteePeriod - ); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_update); + assert_err!(result, Error::::SkippedSyncCommitteePeriod); + assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -546,10 +569,9 @@ fn submit_irrelevant_update() { update.attested_header.slot = checkpoint.header.slot; update.signature_slot = checkpoint.header.slot + 1; - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), - Error::::IrrelevantUpdate - ); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_err!(result, Error::::IrrelevantUpdate); + assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -558,10 +580,9 @@ fn submit_update_with_missing_bootstrap() { let update = Box::new(load_next_finalized_header_update_fixture()); new_tester().execute_with(|| { - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), - Error::::NotBootstrapped - ); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_err!(result, Error::::NotBootstrapped); + assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -574,7 +595,9 @@ fn submit_update_with_invalid_sync_committee_update() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update)); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); // makes update with invalid next_sync_committee >::mutate(>::get(), |x| { @@ -586,10 +609,9 @@ fn submit_update_with_invalid_sync_committee_update() { let next_sync_committee = NextSyncCommitteeUpdate::default(); next_update.next_sync_committee_update = Some(next_sync_committee); - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update), - Error::::InvalidSyncCommitteeUpdate - ); + let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update); + assert_err!(second_result, Error::::InvalidSyncCommitteeUpdate); + assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -612,12 +634,15 @@ fn submit_finalized_header_update_with_too_large_gap() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone())); + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); assert!(>::exists()); - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone()), - Error::::InvalidFinalizedHeaderGap - ); + + let second_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone()); + assert_err!(second_result, Error::::InvalidFinalizedHeaderGap); + assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } @@ -637,14 +662,41 @@ fn submit_finalized_header_update_with_gap_at_limit() { new_tester().execute_with(|| { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone())); + + let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); assert!(>::exists()); + + let second_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone()); assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone()), + second_result, // The test should pass the InvalidFinalizedHeaderGap check, and will fail at the // next check, the merkle proof, because we changed the next_update slots. Error::::InvalidHeaderMerkleProof ); + assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); + }); +} + +#[test] +fn duplicate_sync_committee_updates_are_not_free() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let sync_committee_update = Box::new(load_sync_committee_update_fixture()); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + let result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + // Check that if the same update is submitted, the update is not free. + let second_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update); + assert_err!(second_result, Error::::IrrelevantUpdate); + assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); }); } diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index a031676c6076..871df6d1e51b 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -88,6 +88,7 @@ parameter_types! { impl snowbridge_pallet_ethereum_client::Config for Test { type RuntimeEvent = RuntimeEvent; type ForkVersions = ChainForkVersions; + type FreeHeadersInterval = ConstU32<32>; type WeightInfo = (); } diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index 8f36313e360f..b157ad4356bd 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -12,7 +12,7 @@ use parachains_runtimes_test_utils::{ }; use snowbridge_core::{ChannelId, ParaId}; use snowbridge_pallet_ethereum_client_fixtures::*; -use sp_core::{H160, U256}; +use sp_core::{Get, H160, U256}; use sp_keyring::AccountKeyring::*; use sp_runtime::{traits::Header, AccountId32, DigestItem, SaturatedConversion, Saturating}; use xcm::{ @@ -466,23 +466,37 @@ pub fn ethereum_extrinsic( let initial_checkpoint = make_checkpoint(); let update = make_finalized_header_update(); let sync_committee_update = make_sync_committee_update(); + let mut invalid_update = make_finalized_header_update(); + let mut invalid_sync_committee_update = make_sync_committee_update(); + invalid_update.finalized_header.slot = 4354; + invalid_sync_committee_update.finalized_header.slot = 4354; let alice = Alice; let alice_account = alice.to_account_id(); >::mint_into( - &alice_account.into(), + &alice_account.clone().into(), 10_000_000_000_000_u128.saturated_into::>(), ) .unwrap(); + let balance_before = + >::free_balance(&alice_account.clone().into()); assert_ok!(>::force_checkpoint( RuntimeHelper::::root_origin(), - initial_checkpoint, + initial_checkpoint.clone(), )); + let balance_after_checkpoint = + >::free_balance(&alice_account.clone().into()); let update_call: ::RuntimeCall = snowbridge_pallet_ethereum_client::Call::::submit { - update: Box::new(*update), + update: Box::new(*update.clone()), + } + .into(); + + let invalid_update_call: ::RuntimeCall = + snowbridge_pallet_ethereum_client::Call::::submit { + update: Box::new(*invalid_update), } .into(); @@ -492,12 +506,63 @@ pub fn ethereum_extrinsic( } .into(); + let invalid_update_sync_committee_call: ::RuntimeCall = + snowbridge_pallet_ethereum_client::Call::::submit { + update: Box::new(*invalid_sync_committee_update), + } + .into(); + + // Finalized header update let update_outcome = construct_and_apply_extrinsic(alice, update_call.into()); assert_ok!(update_outcome); + let balance_after_update = + >::free_balance(&alice_account.clone().into()); + + // Invalid finalized header update + let invalid_update_outcome = + construct_and_apply_extrinsic(alice, invalid_update_call.into()); + assert_err!( + invalid_update_outcome, + snowbridge_pallet_ethereum_client::Error::::InvalidUpdateSlot + ); + let balance_after_invalid_update = + >::free_balance(&alice_account.clone().into()); + // Sync committee update let sync_committee_outcome = construct_and_apply_extrinsic(alice, update_sync_committee_call.into()); assert_ok!(sync_committee_outcome); + let balance_after_sync_com_update = + >::free_balance(&alice_account.clone().into()); + + // Invalid sync committee update + let invalid_sync_committee_outcome = + construct_and_apply_extrinsic(alice, invalid_update_sync_committee_call.into()); + assert_err!( + invalid_sync_committee_outcome, + snowbridge_pallet_ethereum_client::Error::::InvalidUpdateSlot + ); + let balance_after_invalid_sync_com_update = + >::free_balance(&alice_account.clone().into()); + + // Assert paid operations are charged and free operations are free + // Checkpoint is a free operation + assert!(balance_before == balance_after_checkpoint); + let gap = + ::FreeHeadersInterval::get(); + // Large enough header gap is free + if update.finalized_header.slot >= initial_checkpoint.header.slot + gap as u64 { + assert!(balance_after_checkpoint == balance_after_update); + } else { + // Otherwise paid + assert!(balance_after_checkpoint > balance_after_update); + } + // An invalid update is paid + assert!(balance_after_update > balance_after_invalid_update); + // A successful sync committee update is free + assert!(balance_after_invalid_update == balance_after_sync_com_update); + // An invalid sync committee update is paid + assert!(balance_after_sync_com_update > balance_after_invalid_sync_com_update); }); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index 01a762d4b99f..a5d798835ac8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -14,9 +14,32 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::{xcm_config::UniversalLocation, Runtime}; -use snowbridge_router_primitives::outbound::EthereumBlobExporter; -use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; +#[cfg(not(feature = "runtime-benchmarks"))] +use crate::XcmRouter; +use crate::{ + xcm_config, xcm_config::UniversalLocation, Balances, EthereumInboundQueue, + EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, + TreasuryAccount, +}; +use parachains_common::{AccountId, Balance}; +use snowbridge_beacon_primitives::{Fork, ForkVersions}; +use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; +use snowbridge_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter}; +use sp_core::H160; +use testnet_parachains_constants::rococo::{ + currency::*, + fee::WeightToFee, + snowbridge::{EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, +}; + +#[cfg(feature = "runtime-benchmarks")] +use benchmark_helpers::DoNothingRouter; +use frame_support::{parameter_types, weights::ConstantMultiplier}; +use pallet_xcm::EnsureXcm; +use sp_runtime::{ + traits::{ConstU32, ConstU8, Keccak256}, + FixedU128, +}; /// Exports message to the Ethereum Gateway contract. pub type SnowbridgeExporter = EthereumBlobExporter< @@ -25,3 +48,173 @@ pub type SnowbridgeExporter = EthereumBlobExporter< snowbridge_pallet_outbound_queue::Pallet, snowbridge_core::AgentIdOf, >; + +// Ethereum Bridge +parameter_types! { + pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); +} + +parameter_types! { + pub const CreateAssetCall: [u8;2] = [53, 0]; + pub const CreateAssetDeposit: u128 = (UNITS / 10) + EXISTENTIAL_DEPOSIT; + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: 1 * UNITS, remote: meth(1) }, + multiplier: FixedU128::from_rational(1, 1), + }; +} + +impl snowbridge_pallet_inbound_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Verifier = snowbridge_pallet_ethereum_client::Pallet; + type Token = Balances; + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmSender = XcmRouter; + #[cfg(feature = "runtime-benchmarks")] + type XcmSender = DoNothingRouter; + type ChannelLookup = EthereumSystem; + type GatewayAddress = EthereumGatewayAddress; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Runtime; + type MessageConverter = MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + ConstU8, + AccountId, + Balance, + >; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type MaxMessageSize = ConstU32<2048>; + type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; + type AssetTransactor = ::AssetTransactor; +} + +impl snowbridge_pallet_outbound_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type Decimals = ConstU8<12>; + type MaxMessagePayloadSize = ConstU32<2048>; + type MaxMessagesPerBlock = ConstU32<32>; + type GasMeter = snowbridge_core::outbound::ConstantGasMeter; + type Balance = Balance; + type WeightToFee = WeightToFee; + type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; + type Channels = EthereumSystem; +} + +#[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [0, 0, 0, 0], // 0x00000000 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 0], // 0x01000000 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 0], // 0x02000000 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 0], // 0x03000000 + epoch: 0, + }, + deneb: Fork { + version: [4, 0, 0, 0], // 0x04000000 + epoch: 0, + } + }; +} + +#[cfg(not(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test)))] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [144, 0, 0, 111], // 0x90000069 + epoch: 0, + }, + altair: Fork { + version: [144, 0, 0, 112], // 0x90000070 + epoch: 50, + }, + bellatrix: Fork { + version: [144, 0, 0, 113], // 0x90000071 + epoch: 100, + }, + capella: Fork { + version: [144, 0, 0, 114], // 0x90000072 + epoch: 56832, + }, + deneb: Fork { + version: [144, 0, 0, 115], // 0x90000073 + epoch: 132608, + }, + }; +} + +impl snowbridge_pallet_ethereum_client::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + // Free consensus update every epoch. Works out to be 225 updates per day. + type FreeHeadersInterval = ConstU32<32>; + type WeightInfo = crate::weights::snowbridge_pallet_ethereum_client::WeightInfo; +} + +impl snowbridge_pallet_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OutboundQueue = EthereumOutboundQueue; + type SiblingOrigin = EnsureXcm; + type AgentIdOf = snowbridge_core::AgentIdOf; + type TreasuryAccount = TreasuryAccount; + type Token = Balances; + type WeightInfo = crate::weights::snowbridge_pallet_system::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type DefaultPricingParameters = Parameters; + type InboundDeliveryCost = EthereumInboundQueue; +} + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmark_helpers { + use crate::{EthereumBeaconClient, Runtime, RuntimeOrigin}; + use codec::Encode; + use snowbridge_beacon_primitives::BeaconHeader; + use snowbridge_pallet_inbound_queue::BenchmarkHelper; + use sp_core::H256; + use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; + + impl BenchmarkHelper for Runtime { + fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) { + EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap(); + } + } + + pub struct DoNothingRouter; + impl SendXcm for DoNothingRouter { + type Ticket = Xcm<()>; + + fn validate( + _dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + Ok((xcm.clone().unwrap(), Assets::new())) + } + fn deliver(xcm: Xcm<()>) -> Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } + } + + impl snowbridge_pallet_system::BenchmarkHelper for () { + fn make_xcm_origin(location: Location) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) + } + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index ca0d89f038cb..c1965e1c8893 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -18,11 +18,13 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ - bridge_common_config, bridge_to_bulletin_config, bridge_to_westend_config, + bridge_common_config, bridge_to_bulletin_config, + bridge_to_ethereum_config::EthereumGatewayAddress, + bridge_to_westend_config, xcm_config::{RelayNetwork, TokenLocation, XcmConfig}, - AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, EthereumGatewayAddress, - Executive, ExistentialDeposit, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, SessionKeys, SignedExtra, TransactionPayment, UncheckedExtrinsic, + AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, + SignedExtra, TransactionPayment, UncheckedExtrinsic, }; use bridge_hub_test_utils::SlotDurations; use codec::{Decode, Encode}; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs new file mode 100644 index 000000000000..82651b11aa51 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -0,0 +1,220 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +#[cfg(not(feature = "runtime-benchmarks"))] +use crate::XcmRouter; +use crate::{ + xcm_config, + xcm_config::{TreasuryAccount, UniversalLocation}, + Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, + RuntimeEvent, TransactionByteFee, +}; +use parachains_common::{AccountId, Balance}; +use snowbridge_beacon_primitives::{Fork, ForkVersions}; +use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; +use snowbridge_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter}; +use sp_core::H160; +use testnet_parachains_constants::westend::{ + currency::*, + fee::WeightToFee, + snowbridge::{EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, +}; + +#[cfg(feature = "runtime-benchmarks")] +use benchmark_helpers::DoNothingRouter; +use frame_support::{parameter_types, weights::ConstantMultiplier}; +use pallet_xcm::EnsureXcm; +use sp_runtime::{ + traits::{ConstU32, ConstU8, Keccak256}, + FixedU128, +}; + +/// Exports message to the Ethereum Gateway contract. +pub type SnowbridgeExporter = EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + snowbridge_pallet_outbound_queue::Pallet, + snowbridge_core::AgentIdOf, +>; + +// Ethereum Bridge +parameter_types! { + pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); +} + +parameter_types! { + pub const CreateAssetCall: [u8;2] = [53, 0]; + pub const CreateAssetDeposit: u128 = (UNITS / 10) + EXISTENTIAL_DEPOSIT; + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: 1 * UNITS, remote: meth(1) }, + multiplier: FixedU128::from_rational(1, 1), + }; +} + +impl snowbridge_pallet_inbound_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Verifier = snowbridge_pallet_ethereum_client::Pallet; + type Token = Balances; + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmSender = XcmRouter; + #[cfg(feature = "runtime-benchmarks")] + type XcmSender = DoNothingRouter; + type ChannelLookup = EthereumSystem; + type GatewayAddress = EthereumGatewayAddress; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Runtime; + type MessageConverter = MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + ConstU8, + AccountId, + Balance, + >; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type MaxMessageSize = ConstU32<2048>; + type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; + type AssetTransactor = ::AssetTransactor; +} + +impl snowbridge_pallet_outbound_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type Decimals = ConstU8<12>; + type MaxMessagePayloadSize = ConstU32<2048>; + type MaxMessagesPerBlock = ConstU32<32>; + type GasMeter = snowbridge_core::outbound::ConstantGasMeter; + type Balance = Balance; + type WeightToFee = WeightToFee; + type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; + type Channels = EthereumSystem; +} + +#[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [0, 0, 0, 0], // 0x00000000 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 0], // 0x01000000 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 0], // 0x02000000 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 0], // 0x03000000 + epoch: 0, + }, + deneb: Fork { + version: [4, 0, 0, 0], // 0x04000000 + epoch: 0, + } + }; +} + +#[cfg(not(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test)))] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [144, 0, 0, 111], // 0x90000069 + epoch: 0, + }, + altair: Fork { + version: [144, 0, 0, 112], // 0x90000070 + epoch: 50, + }, + bellatrix: Fork { + version: [144, 0, 0, 113], // 0x90000071 + epoch: 100, + }, + capella: Fork { + version: [144, 0, 0, 114], // 0x90000072 + epoch: 56832, + }, + deneb: Fork { + version: [144, 0, 0, 115], // 0x90000073 + epoch: 132608, + }, + }; +} + +impl snowbridge_pallet_ethereum_client::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type FreeHeadersInterval = ConstU32<32>; + type WeightInfo = crate::weights::snowbridge_pallet_ethereum_client::WeightInfo; +} + +impl snowbridge_pallet_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OutboundQueue = EthereumOutboundQueue; + type SiblingOrigin = EnsureXcm; + type AgentIdOf = snowbridge_core::AgentIdOf; + type TreasuryAccount = TreasuryAccount; + type Token = Balances; + type WeightInfo = crate::weights::snowbridge_pallet_system::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type DefaultPricingParameters = Parameters; + type InboundDeliveryCost = EthereumInboundQueue; +} + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmark_helpers { + use crate::{EthereumBeaconClient, Runtime, RuntimeOrigin}; + use codec::Encode; + use snowbridge_beacon_primitives::BeaconHeader; + use snowbridge_pallet_inbound_queue::BenchmarkHelper; + use sp_core::H256; + use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; + + impl BenchmarkHelper for Runtime { + fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) { + EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap(); + } + } + + pub struct DoNothingRouter; + impl SendXcm for DoNothingRouter { + type Ticket = Xcm<()>; + + fn validate( + _dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + Ok((xcm.clone().unwrap(), Assets::new())) + } + fn deliver(xcm: Xcm<()>) -> Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } + } + + impl snowbridge_pallet_system::BenchmarkHelper for () { + fn make_xcm_origin(location: Location) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) + } + } +}