From abd17d29b9c2f3ba3b89f083bfb78190857190e1 Mon Sep 17 00:00:00 2001 From: freakstatic Date: Mon, 22 Apr 2024 02:04:13 +0100 Subject: [PATCH] WIP: argo bridge --- Cargo.lock | 27 ++ bin/node/src/chain_spec/argo_bridge_config.rs | 21 + bin/node/src/chain_spec/mod.rs | 15 +- bin/utils/chain-spec-builder/src/main.rs | 12 +- runtime-modules/argo-bridge/Cargo.toml | 56 +++ runtime-modules/argo-bridge/src/Untitled-2 | 6 + .../argo-bridge/src/benchmarking.rs | 100 +++++ runtime-modules/argo-bridge/src/errors.rs | 35 ++ runtime-modules/argo-bridge/src/events.rs | 26 ++ runtime-modules/argo-bridge/src/lib.rs | 221 +++++++++++ runtime-modules/argo-bridge/src/tests/mock.rs | 199 ++++++++++ runtime-modules/argo-bridge/src/tests/mod.rs | 370 ++++++++++++++++++ runtime-modules/argo-bridge/src/types.rs | 61 +++ runtime-modules/argo-bridge/src/weights.rs | 69 ++++ runtime-modules/proposals/codex/Cargo.toml | 2 + .../proposals/codex/src/benchmarking.rs | 32 ++ runtime-modules/proposals/codex/src/lib.rs | 19 + .../proposals/codex/src/tests/mock.rs | 1 + runtime-modules/proposals/codex/src/types.rs | 7 + .../proposals/codex/src/weights.rs | 20 + runtime/Cargo.toml | 4 + .../integration/proposals/proposal_encoder.rs | 3 + runtime/src/lib.rs | 16 + .../src/proposals_configuration/defaults.rs | 14 + runtime/src/proposals_configuration/mod.rs | 3 + .../src/proposals_configuration/playground.rs | 14 + .../src/proposals_configuration/staging.rs | 14 + .../src/proposals_configuration/testing.rs | 14 + 28 files changed, 1374 insertions(+), 7 deletions(-) create mode 100644 bin/node/src/chain_spec/argo_bridge_config.rs create mode 100644 runtime-modules/argo-bridge/Cargo.toml create mode 100644 runtime-modules/argo-bridge/src/Untitled-2 create mode 100644 runtime-modules/argo-bridge/src/benchmarking.rs create mode 100644 runtime-modules/argo-bridge/src/errors.rs create mode 100644 runtime-modules/argo-bridge/src/events.rs create mode 100644 runtime-modules/argo-bridge/src/lib.rs create mode 100644 runtime-modules/argo-bridge/src/tests/mock.rs create mode 100644 runtime-modules/argo-bridge/src/tests/mod.rs create mode 100644 runtime-modules/argo-bridge/src/types.rs create mode 100644 runtime-modules/argo-bridge/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 70a2ae9597..1ebd18333f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3463,6 +3463,7 @@ dependencies = [ "lazy_static", "lite-json", "log", + "pallet-argo-bridge", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", @@ -5048,6 +5049,31 @@ dependencies = [ "libm 0.1.4", ] +[[package]] +name = "pallet-argo-bridge" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-common", + "pallet-insecure-randomness-collective-flip", + "pallet-membership", + "pallet-staking-handler", + "pallet-storage", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-storage", +] + [[package]] name = "pallet-authority-discovery" version = "4.0.0-dev" @@ -5570,6 +5596,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", + "pallet-argo-bridge", "pallet-bags-list", "pallet-balances", "pallet-common", diff --git a/bin/node/src/chain_spec/argo_bridge_config.rs b/bin/node/src/chain_spec/argo_bridge_config.rs new file mode 100644 index 0000000000..13ec4d4ab6 --- /dev/null +++ b/bin/node/src/chain_spec/argo_bridge_config.rs @@ -0,0 +1,21 @@ +use node_runtime::{argo_bridge::types::BridgeStatus, ArgoBridgeConfig}; + +pub fn production_config() -> ArgoBridgeConfig { + ArgoBridgeConfig { + status: BridgeStatus::Paused, + mint_allowance: 0, + bridging_fee: 0, + thawn_duration: 1, + ..Default::default() + } +} + +pub fn testing_config() -> ArgoBridgeConfig { + ArgoBridgeConfig { + status: BridgeStatus::Paused, + mint_allowance: 0, + bridging_fee: 0, + thawn_duration: 1, + ..Default::default() + } +} diff --git a/bin/node/src/chain_spec/mod.rs b/bin/node/src/chain_spec/mod.rs index 26ec138914..96c900703b 100644 --- a/bin/node/src/chain_spec/mod.rs +++ b/bin/node/src/chain_spec/mod.rs @@ -19,6 +19,7 @@ // Example: voting_period: 1 * DAY #![allow(clippy::identity_op)] +pub mod argo_bridge_config; pub mod content_config; pub mod council_config; pub mod forum_config; @@ -30,10 +31,10 @@ pub use grandpa_primitives::AuthorityId as GrandpaId; use node_runtime::{ constants::currency::{DOLLARS, MIN_NOMINATOR_BOND, MIN_VALIDATOR_BOND}, - wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, Block, ContentConfig, - ExistentialDeposit, GrandpaConfig, ImOnlineConfig, MaxNominations, ProjectTokenConfig, - SessionConfig, SessionKeys, StakerStatus, StakingConfig, StorageConfig, SystemConfig, - TransactionPaymentConfig, VestingConfig, + wasm_binary_unwrap, ArgoBridgeConfig, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, + Block, ContentConfig, ExistentialDeposit, GrandpaConfig, ImOnlineConfig, MaxNominations, + ProjectTokenConfig, SessionConfig, SessionKeys, StakerStatus, StakingConfig, StorageConfig, + SystemConfig, TransactionPaymentConfig, VestingConfig, }; pub use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sc_chain_spec::ChainSpecExtension; @@ -183,6 +184,7 @@ pub fn testnet_genesis( content_cfg: ContentConfig, storage_cfg: StorageConfig, project_token_cfg: ProjectTokenConfig, + argo_bridge_cfg: ArgoBridgeConfig, ) -> GenesisConfig { // staking benchmakrs is not sensitive to actual value of min bonds so // accounts are not funded with sufficient funds and fail with InsufficientBond err @@ -335,6 +337,7 @@ pub fn testnet_genesis( content: content_cfg, storage: storage_cfg, project_token: project_token_cfg, + argo_bridge: argo_bridge_cfg, proposals_discussion: Default::default(), members: Default::default(), } @@ -354,6 +357,7 @@ fn development_config_genesis() -> GenesisConfig { content_config::testing_config(), storage_config::testing_config(), project_token_config::testing_config(), + argo_bridge_config::testing_config(), ) } @@ -387,6 +391,7 @@ fn local_testnet_genesis() -> GenesisConfig { content_config::testing_config(), storage_config::testing_config(), project_token_config::testing_config(), + argo_bridge_config::testing_config(), ) } @@ -417,6 +422,7 @@ fn prod_test_config_genesis() -> GenesisConfig { content_config::production_config(), storage_config::production_config(), project_token_config::production_config(), + argo_bridge_config::production_config(), ) } @@ -454,6 +460,7 @@ pub(crate) mod tests { content_config::testing_config(), storage_config::testing_config(), project_token_config::testing_config(), + argo_bridge_config::testing_config(), ) } diff --git a/bin/utils/chain-spec-builder/src/main.rs b/bin/utils/chain-spec-builder/src/main.rs index c2bf429ce0..e2356d29c8 100644 --- a/bin/utils/chain-spec-builder/src/main.rs +++ b/bin/utils/chain-spec-builder/src/main.rs @@ -24,9 +24,9 @@ use std::{ }; use joystream_node::chain_spec::{ - self, content_config, initial_balances, joy_chain_spec_properties, project_token_config, - storage_config, AccountId, AuthorityDiscoveryId, BabeId, GrandpaId, ImOnlineId, - JOY_ADDRESS_PREFIX, + self, argo_bridge_config, content_config, initial_balances, joy_chain_spec_properties, + project_token_config, storage_config, AccountId, AuthorityDiscoveryId, BabeId, GrandpaId, + ImOnlineId, JOY_ADDRESS_PREFIX, }; use sc_chain_spec::ChainType; @@ -267,6 +267,11 @@ fn genesis_constructor( _ => project_token_config::testing_config(), }; + let argo_bridge_cfg = match deployment { + ChainDeployment::mainnet => argo_bridge_config::production_config(), + _ => argo_bridge_config::testing_config(), + }; + chain_spec::testnet_genesis( fund_accounts, authorities, @@ -277,6 +282,7 @@ fn genesis_constructor( content_cfg, storage_cfg, project_token_cfg, + argo_bridge_cfg, ) } diff --git a/runtime-modules/argo-bridge/Cargo.toml b/runtime-modules/argo-bridge/Cargo.toml new file mode 100644 index 0000000000..ed5141e95e --- /dev/null +++ b/runtime-modules/argo-bridge/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "pallet-argo-bridge" +version = '1.0.0' +authors = ['Joystream contributors'] +edition = '2018' + +[dependencies] +sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +sp-std = { package = 'sp-std', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +common = { package = 'pallet-common', default-features = false, path = '../common'} +storage = { package = 'pallet-storage', default-features = false, path = '../storage'} +balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +membership = { package = 'pallet-membership', default-features = false, path = '../membership'} +codec = { package = 'parity-scale-codec', version = '3.1.2', default-features = false, features = ['derive'] } +serde = {version = '1.0.101', features = ['derive'], optional = true} +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9', optional = true } + +# Benchmarking dependencies +frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9', optional = true} + +[dev-dependencies] +sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9' } +randomness-collective-flip = { package = 'pallet-insecure-randomness-collective-flip', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'} +pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} +sp-storage = { package = 'sp-storage', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9'} + +[features] +default = ['std', 'runtime-benchmarks'] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + 'sp-core', + "common/runtime-benchmarks", +] +std = [ + 'sp-std/std', + 'sp-io/std', + 'sp-runtime/std', + 'frame-support/std', + 'frame-system/std', + 'sp-arithmetic/std', + 'common/std', + 'storage/std', + 'balances/std', + 'membership/std', + 'codec/std', + 'serde', + 'scale-info/std', + 'frame-benchmarking?/std', +] +try-runtime = ["frame-support/try-runtime"] diff --git a/runtime-modules/argo-bridge/src/Untitled-2 b/runtime-modules/argo-bridge/src/Untitled-2 new file mode 100644 index 0000000000..af54e25bc6 --- /dev/null +++ b/runtime-modules/argo-bridge/src/Untitled-2 @@ -0,0 +1,6 @@ + + +for hash use +keccak256(bytes memory) returns (bytes32) − computes the Keccak-256 hash of the input. +ripemd160(bytes memory) returns (bytes20) − compute RIPEMD-160 hash of the input. +sha256(bytes memory) returns (bytes32) − computes the SHA-256 hash of the input. \ No newline at end of file diff --git a/runtime-modules/argo-bridge/src/benchmarking.rs b/runtime-modules/argo-bridge/src/benchmarking.rs new file mode 100644 index 0000000000..2a27e23dc3 --- /dev/null +++ b/runtime-modules/argo-bridge/src/benchmarking.rs @@ -0,0 +1,100 @@ +#![cfg(feature = "runtime-benchmarks")] + +use frame_support::{assert_err, assert_ok}; + +use super::*; +use crate::types::*; +use crate::Module as ArgoBridge; +use balances::Pallet as Balances; +use core::convert::TryFrom; +use frame_benchmarking::v1::{account, benchmarks}; +use frame_system::Pallet as System; +use frame_system::{EventRecord, RawOrigin}; +use sp_runtime::traits::One; +use sp_std::convert::TryInto; +use sp_std::vec; + +use crate::{BridgeConstraints, BridgeStatus, RemoteAccount, RemoteTransfer}; + +const SEED: u32 = 0; + +// We create this trait because we need to be compatible with the runtime +// in the mock for tests. In that case we need to be able to have `membership_id == account_id` +// We can't create an account from an `u32` or from a memberhsip_dd, +// so this trait allows us to get an account id from an u32, in the case of `64` which is what +// the mock use we get the parameter as a return. +// In the case of `AccountId32` we use the method provided by `frame_benchmarking` to get an +// AccountId. +pub trait CreateAccountId { + fn create_account_id(id: u32) -> Self; +} + +impl CreateAccountId for u64 { + fn create_account_id(id: u32) -> Self { + id.into() + } +} + +impl CreateAccountId for u32 { + fn create_account_id(id: u32) -> Self { + id.into() + } +} + +impl CreateAccountId for sp_core::crypto::AccountId32 { + fn create_account_id(id: u32) -> Self { + account::("default", id, SEED) + } +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + assert!( + !events.is_empty(), + "If you are checking for last event there must be at least 1 event" + ); + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +benchmarks! { + where_clause { + where + T: balances::Config, + T::AccountId: CreateAccountId + } + + request_outbound_transfer{ + let fee: BalanceOf = 10u32.into(); + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(T::AccountId::create_account_id(1u32.into())), + pauser_accounts: Some(vec![T::AccountId::create_account_id(2u32.into())]), + bridging_fee: Some(fee), + thawn_duration: None::, + remote_chains: Some(remote_chains) + }; + assert_ok!(ArgoBridge::::update_bridge_constrains( + RawOrigin::Root.into(), + parameters + )); + + let dest_account = RemoteAccount { + account: [0; 32], + chain_id: 1, + }; + let sender = T::AccountId::create_account_id(1u32.into()); + let origin = RawOrigin::Signed(sender); + let transfer_id = ArgoBridge::::next_transfer_id(); + let amount = 100u32.into(); + }: _(origin, dest_account, amount, fee) + verify { + let sender = T::AccountId::create_account_id(1u32.into()); + assert_last_event::( + RawEvent::OutboundTransferRequested(transfer_id, sender, dest_account, amount, fee).into()); + } +} + +#[cfg(test)] +mod tests {} diff --git a/runtime-modules/argo-bridge/src/errors.rs b/runtime-modules/argo-bridge/src/errors.rs new file mode 100644 index 0000000000..7d5f668257 --- /dev/null +++ b/runtime-modules/argo-bridge/src/errors.rs @@ -0,0 +1,35 @@ +use crate::Module; +use frame_support::decl_error; +use sp_std::convert::TryInto; + +decl_error! { + pub enum Error for Module { + + /// Bridge not on active state + BridgeNotActive, + + /// Insufficient JOY Balance to cover the transaction costs + InsufficientJoyBalance, + + /// The bridging_fee changed since the request transfer + FeeDifferentThanExpected, + + /// Not enough mint allowance for transaction + InsufficienBridgMintAllowance, + + /// Operator account required + NotOperatorAccount, + + /// Pauser account required + NotPauserAccount, + + /// Number of pauser accounts over the maximum allowed + InvalidNumberOfPauserAccounts, + + /// Current block is lower than thawn_started_at + thawn_duration + ThawnNotFinished, + + /// ChainId is not on the list of the supported chains + NotSupportedRemoteChainId + } +} diff --git a/runtime-modules/argo-bridge/src/events.rs b/runtime-modules/argo-bridge/src/events.rs new file mode 100644 index 0000000000..7facc1cb53 --- /dev/null +++ b/runtime-modules/argo-bridge/src/events.rs @@ -0,0 +1,26 @@ +#![allow(clippy::unused_unit)] + +use frame_support::decl_event; + +use crate::{RemoteAccount, RemoteTransfer, TransferId}; + +use crate::types::*; + +// Balance type alias +type BalanceOf = ::Balance; + +decl_event!( + pub enum Event + where + AccountId = ::AccountId, + Balance = BalanceOf, + BridgeConstraints = BridgeConstraintsOf, + { + OutboundTransferRequested(TransferId, AccountId, RemoteAccount, Balance, Balance), + InboundTransferFinalized(RemoteTransfer, AccountId, Balance), + BridgePaused(AccountId), + BridgeThawnStarted(AccountId), + BridgeThawnFinished(), + BridgeConfigUpdated(BridgeConstraints), + } +); diff --git a/runtime-modules/argo-bridge/src/lib.rs b/runtime-modules/argo-bridge/src/lib.rs new file mode 100644 index 0000000000..9942d94e1f --- /dev/null +++ b/runtime-modules/argo-bridge/src/lib.rs @@ -0,0 +1,221 @@ +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr( + not(any(test, feature = "runtime-benchmarks")), + deny(clippy::panic), + deny(clippy::panic_in_result_fn), + deny(clippy::unwrap_used), + deny(clippy::expect_used), + deny(clippy::indexing_slicing), + deny(clippy::integer_arithmetic), + deny(clippy::match_on_vec_items), + deny(clippy::unreachable) +)] + +#[cfg(not(any(test, feature = "runtime-benchmarks")))] +#[allow(unused_imports)] +#[macro_use] +extern crate common; + +use core::{convert::TryInto, default::Default}; +use frame_support::{ + decl_module, decl_storage, + dispatch::{marker::Copy, DispatchResult}, + ensure, + storage::bounded_vec::BoundedVec, + traits::{ConstU32, Currency, Get}, +}; +use frame_system::{ensure_root, ensure_signed}; + +use sp_std::vec; + +// crate modules +mod benchmarking; +mod errors; +mod events; +mod tests; +pub mod types; +pub mod weights; +pub use weights::WeightInfo; + +// crate imports +use common::costs::{burn_from_usable, has_sufficient_balance_for_payment}; +pub use errors::Error; +pub use events::{Event, RawEvent}; +use types::*; +type WeightInfoArgo = ::WeightInfo; + +pub trait Config: frame_system::Config + balances::Config { + // /// Events + type RuntimeEvent: From> + Into<::RuntimeEvent>; + + type MaxPauserAccounts: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Defines the default bridging fee. + type DefaultBridgingFee: Get>; +} + +decl_storage! { generate_storage_info + trait Store for Module as ArgoBridge { + pub Status get(fn status) config(): BridgeStatus; + + /// Account ID that operates the bridge + pub OperatorAccount get(fn operator_account): Option; + + /// List of account IDs with permission to pause the bridge operations + pub PauserAccounts get(fn pauser_accounts): BoundedVec; + + /// Number of tokens that the bridge pallet is able to mint + pub MintAllowance get(fn mint_allowance) config(): BalanceOf; + + /// Amount of JOY burned as a fee for each transfer + pub BridgingFee get(fn bridging_fee) config(): BalanceOf; + + /// Number of blocks needed before bridge unpause can be finalised + pub ThawnDuration get(fn thawn_duration) config(): T::BlockNumber; + + pub NextTransferId get(fn next_transfer_id): TransferId; + + pub RemoteChains get(fn remote_chains): BoundedVec>; + } +} + +decl_module! { + pub struct Module for enum Call + where + origin: T::RuntimeOrigin + { + + /// Default deposit_event() handler + fn deposit_event() = default; + + #[weight = WeightInfoArgo::::request_outbound_transfer()] + pub fn request_outbound_transfer(origin, dest_account: RemoteAccount, amount: BalanceOf, expected_fee: BalanceOf) -> DispatchResult { + ensure!(Self::status() == BridgeStatus::Active, Error::::BridgeNotActive); + ensure!(RemoteChains::get().contains(&dest_account.chain_id), Error::::NotSupportedRemoteChainId); + + let fee = Self::bridging_fee(); + ensure!(fee == expected_fee, Error::::FeeDifferentThanExpected); + + let amount_with_fees = fee + amount; + let sender = ensure_signed(origin)?; + ensure!(has_sufficient_balance_for_payment::(&sender, amount_with_fees), Error::::InsufficientJoyBalance); + + burn_from_usable::(&sender, amount_with_fees)?; + >::put(Self::mint_allowance() + amount); + + let transfer_id = NextTransferId::get(); + Self::deposit_event(RawEvent::OutboundTransferRequested(transfer_id, sender, dest_account, amount, fee)); + NextTransferId::put(transfer_id + 1); + + Ok(()) + } + + #[weight = WeightInfoArgo::::finalize_inbound_transfer()] + pub fn finalize_inbound_transfer(origin, remote_transfer: RemoteTransfer, dest_account: T::AccountId, amount: BalanceOf) -> DispatchResult { + ensure!(!Self::operator_account().is_none(), Error::::NotOperatorAccount); + let caller = ensure_signed(origin)?; + ensure!(caller == Self::operator_account().unwrap(), Error::::NotOperatorAccount); + + ensure!(Self::status() == BridgeStatus::Active, Error::::BridgeNotActive); + ensure!(amount < Self::mint_allowance(), Error::::InsufficienBridgMintAllowance); + + ensure!(RemoteChains::get().contains(&remote_transfer.chain_id), Error::::NotSupportedRemoteChainId); + + >::put(Self::mint_allowance() - amount); + let _ = balances::Pallet::::deposit_creating( + &caller, + amount + ); + + Self::deposit_event(RawEvent::InboundTransferFinalized(remote_transfer, dest_account, amount)); + + Ok(()) + } + + #[weight = WeightInfoArgo::::pause_bridge()] + pub fn pause_bridge(origin) -> DispatchResult { + let caller = ensure_signed(origin)?; + let accounts = Self::pauser_accounts(); + ensure!(accounts.contains(&caller), Error::::NotPauserAccount); + + >::put(BridgeStatus::Paused); + Self::deposit_event(RawEvent::BridgePaused(caller)); + + Ok(()) + } + + #[weight = WeightInfoArgo::::init_unpause_bridge()] + pub fn init_unpause_bridge(origin) -> DispatchResult{ + let caller = ensure_signed(origin)?; + ensure!(Self::pauser_accounts().contains(&caller), Error::::NotPauserAccount); + + let current_block = >::block_number(); + >::put(BridgeStatus::Thawn { thawn_ends_at: current_block + Self::thawn_duration()}); + Self::deposit_event(RawEvent::BridgeThawnStarted(caller)); + + Ok(()) + } + + #[weight = WeightInfoArgo::::finish_unpause_bridge()] + pub fn finish_unpause_bridge(origin) -> DispatchResult { + let caller = ensure_signed(origin)?; + ensure!(!Self::operator_account().is_none(), Error::::NotOperatorAccount); + ensure!(caller == Self::operator_account().unwrap(), Error::::NotOperatorAccount); + + let current_block = >::block_number(); + ensure!( + matches!(Self::status(), BridgeStatus::Thawn { thawn_ends_at } + if current_block >= thawn_ends_at), Error::::ThawnNotFinished); + + >::put(BridgeStatus::Active); + Self::deposit_event(RawEvent::BridgeThawnFinished()); + + Ok(()) + } + + #[weight = WeightInfoArgo::::update_bridge_constrains()] + pub fn update_bridge_constrains(origin, parameters: BridgeConstraintsOf) -> DispatchResult { + ensure_root(origin)?; + + if let Some(ref new_operator_account) = parameters.operator_account { + >::put(new_operator_account); + } + + if let Some(ref new_pauser_accounts) = parameters.pauser_accounts { + ensure!(new_pauser_accounts.len() <= T::MaxPauserAccounts::get().try_into().unwrap(), Error::::InvalidNumberOfPauserAccounts); + >::put(BoundedVec::truncate_from(new_pauser_accounts.to_vec())); + } + + if let Some(new_bridging_fee) = parameters.bridging_fee { + >::put(new_bridging_fee); + } + + if let Some(new_thawn_duration) = parameters.thawn_duration { + >::put(new_thawn_duration); + } + + if let Some(ref new_remote_chains) = parameters.remote_chains { + RemoteChains::put(new_remote_chains); + } + + Self::deposit_event(RawEvent::BridgeConfigUpdated(parameters)); + + Ok(()) + } + + } +} + +/// Module implementation +impl Module {} + +impl frame_support::traits::Hooks for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_: T::BlockNumber) -> Result<(), &'static str> { + Ok(()) + } +} diff --git a/runtime-modules/argo-bridge/src/tests/mock.rs b/runtime-modules/argo-bridge/src/tests/mock.rs new file mode 100644 index 0000000000..49ef1d6089 --- /dev/null +++ b/runtime-modules/argo-bridge/src/tests/mock.rs @@ -0,0 +1,199 @@ +#![cfg(test)] + +use crate as argo_bridge; +use crate::*; + +use frame_support::{ + parameter_types, + traits::{Currency, OnFinalize, OnInitialize}, +}; + +use common::{ + council, + membership::{MemberOriginValidator, MembershipInfoProvider}, +}; +use common::{ + locks::{BoundStakingAccountLockId, InvitedMemberLockId}, + numerical::one_plus_interest_pow_fixed, +}; +use frame_support::{ + ensure, + traits::{ConstU16, ConstU32, ConstU64, LockIdentifier, WithdrawReasons}, + PalletId, +}; +use frame_system::ensure_signed; +use sp_arithmetic::{FixedPointNumber, Perbill}; +use sp_io::TestExternalities; +use sp_runtime::testing::{Header, H256}; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; +use sp_runtime::{DispatchError, DispatchResult, PerThing, Permill}; +use sp_std::convert::{TryFrom, TryInto}; +use staking_handler::{LockComparator, StakingHandler}; + +// Crate aliases +type BalanceOf = ::Balance; +pub type Balance = BalanceOf; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +pub type AccountId = ::AccountId; +pub type BlockNumber = ::BlockNumber; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MinimumPeriod: u64 = 5; + pub const ExistentialDeposit: u64 = 10; + pub const DefaultMembershipPrice: u64 = 100; + pub const DefaultInitialInvitationBalance: u64 = 100; + pub const DefaultMemberInvitesCount: u32 = 2; +} + +// Config constants +parameter_types! { + pub const MaxPauserAccounts: u32 = 10; + pub const DefaultBridgingFee: Balance = 1000; +} + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: balances, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + ArgoBridge: argo_bridge::{Pallet, Call, Storage, Event}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +impl balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = ConstU32<2>; + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaxPauserAccounts = MaxPauserAccounts; + type WeightInfo = argo_bridge::weights::SubstrateWeight; + type DefaultBridgingFee = DefaultBridgingFee; +} + +pub fn default_genesis_config() -> argo_bridge::GenesisConfig { + argo_bridge::GenesisConfig:: { + status: BridgeStatus::Active, + mint_allowance: 0, + bridging_fee: DefaultBridgingFee::get(), + thawn_duration: 1, + } +} + +pub fn build_test_externalities(mint_allowance: Balance) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let mut configs = default_genesis_config(); + configs.mint_allowance = mint_allowance; + configs.assimilate_storage(&mut t).unwrap(); + + t.into() +} + +/// Generate enviroment with test externalities +pub fn with_test_externalities R>(f: F) -> R { + /* + Events are not emitted on block 0. + So any dispatchable calls made during genesis block formation will have no events emitted. + https://substrate.dev/recipes/2-appetizers/4-events.html + */ + let func = || { + increase_block_number_by(1); + f() + }; + + build_test_externalities(0).execute_with(func) +} + +pub fn with_test_externalities_custom_mint_allowance R>( + mint_allowance: Balance, + f: F, +) -> R { + /* + Events are not emitted on block 0. + So any dispatchable calls made during genesis block formation will have no events emitted. + https://substrate.dev/recipes/2-appetizers/4-events.html + */ + let func = || { + increase_block_number_by(1); + f() + }; + + build_test_externalities(mint_allowance).execute_with(func) +} + +/// Moving past n blocks +pub fn increase_block_number_by(n: u64) { + let init_block = System::block_number(); + (0..=n).for_each(|offset| { + >::on_finalize(System::block_number()); + >::on_finalize(System::block_number()); + System::set_block_number(init_block.saturating_add(offset)); + >::on_initialize(System::block_number()); + >::on_initialize(System::block_number()); + }) +} + +#[macro_export] +macro_rules! joy { + ($bal:expr) => { + Balance::from($bal as u64) + }; +} + +#[macro_export] +macro_rules! account { + ($acc:expr) => { + AccountId::from($acc as u64) + }; +} diff --git a/runtime-modules/argo-bridge/src/tests/mod.rs b/runtime-modules/argo-bridge/src/tests/mod.rs new file mode 100644 index 0000000000..ae8b0a50a5 --- /dev/null +++ b/runtime-modules/argo-bridge/src/tests/mod.rs @@ -0,0 +1,370 @@ +#![cfg(test)] + +use core::convert::TryFrom; + +use frame_support::{assert_err, assert_ok}; +use sp_runtime::BoundedVec; + +use crate::{ + account, joy, + tests::mock::{increase_block_number_by, AccountId, Balance, Balances, BlockNumber, Test}, + BridgeConstraints, BridgeStatus, RemoteAccount, RemoteTransfer, +}; + +use self::mock::{ + with_test_externalities, with_test_externalities_custom_mint_allowance, ArgoBridge, + RuntimeOrigin, +}; +use crate::Error; + +mod mock; + +#[test] +fn request_outbound_transfer_success() { + with_test_externalities(|| { + let fee = joy!(10); + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: Some(vec![account!(2), account!(3)]), + bridging_fee: Some(fee), + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + Balances::set_balance(RuntimeOrigin::root(), account!(1), joy!(1020), joy!(0)).unwrap(); + + let remote_account = RemoteAccount { + account: [0; 32], + chain_id: 1, + }; + let result = ArgoBridge::request_outbound_transfer( + RuntimeOrigin::signed(account!(1)), + remote_account, + joy!(1000), + joy!(fee), + ); + assert_ok!(result); + }); +} + +#[test] +fn request_outbound_transfer_with_bridge_paused() { + with_test_externalities(|| { + let fee = joy!(10); + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: Some(vec![account!(2), account!(3)]), + bridging_fee: Some(fee), + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + ArgoBridge::update_bridge_constrains(RuntimeOrigin::root(), parameters).unwrap(); + + ArgoBridge::pause_bridge(RuntimeOrigin::signed(account!(2))).unwrap(); + + let remote_account = RemoteAccount { + account: [0; 32], + chain_id: 1, + }; + let result = ArgoBridge::request_outbound_transfer( + RuntimeOrigin::signed(account!(1)), + remote_account, + joy!(1000), + fee, + ); + assert_err!(result, Error::::BridgeNotActive); + }); +} + +#[test] +fn request_outbound_transfer_with_insufficient_balance() { + with_test_externalities(|| { + let fee = joy!(10); + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: Some(vec![account!(2), account!(3)]), + bridging_fee: Some(fee), + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let transfer_amount = joy!(1000); + assert_ok!(Balances::set_balance( + RuntimeOrigin::root(), + account!(1), + transfer_amount + fee - 1, + joy!(0) + )); + + let remote_account = RemoteAccount { + account: [0; 32], + chain_id: 1, + }; + let result = ArgoBridge::request_outbound_transfer( + RuntimeOrigin::signed(account!(1)), + remote_account, + transfer_amount, + fee, + ); + assert_err!(result, Error::::InsufficientJoyBalance); + }); +} + +#[test] +fn request_outbound_transfer_with_unexpected_fee() { + with_test_externalities(|| { + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: Some(vec![account!(2), account!(3)]), + bridging_fee: Some(joy!(20)), + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let transfer_amount = joy!(1000); + assert_ok!(Balances::set_balance( + RuntimeOrigin::root(), + account!(1), + transfer_amount + 50, + joy!(0) + )); + + let remote_account = RemoteAccount { + account: [0; 32], + chain_id: 1, + }; + let result = ArgoBridge::request_outbound_transfer( + RuntimeOrigin::signed(account!(1)), + remote_account, + transfer_amount, + joy!(10), + ); + assert_err!(result, Error::::FeeDifferentThanExpected); + }); +} + +#[test] +fn finalize_inbound_transfer_success() { + with_test_externalities_custom_mint_allowance(joy!(1000), || { + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: None, + bridging_fee: None, + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let remote_transfer = RemoteTransfer { id: 0, chain_id: 1 }; + let result = ArgoBridge::finalize_inbound_transfer( + RuntimeOrigin::signed(account!(1)), + remote_transfer, + account!(2), + 1000, + ); + assert_err!(result, Error::::InsufficienBridgMintAllowance); + }); +} + +#[test] +fn finalize_inbound_transfer_with_no_operator_account() { + with_test_externalities(|| { + let remote_transfer = RemoteTransfer { id: 0, chain_id: 1 }; + let result = ArgoBridge::finalize_inbound_transfer( + RuntimeOrigin::signed(account!(1)), + remote_transfer, + account!(2), + 1000, + ); + assert_err!(result, Error::::NotOperatorAccount); + }); +} + +#[test] +fn finalize_inbound_transfer_with_unauthorized_account() { + with_test_externalities(|| { + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: None, + bridging_fee: None, + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let remote_transfer = RemoteTransfer { id: 0, chain_id: 1 }; + let result = ArgoBridge::finalize_inbound_transfer( + RuntimeOrigin::signed(account!(2)), + remote_transfer, + account!(2), + 1000, + ); + assert_err!(result, Error::::NotOperatorAccount); + }); +} + +#[test] +fn finalize_inbound_transfer_with_insufficient_bridge_mint() { + with_test_externalities(|| { + let remote_chains = BoundedVec::try_from(vec![1u32]).unwrap(); + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: None, + bridging_fee: None, + thawn_duration: None, + remote_chains: Some(remote_chains), + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let remote_transfer = RemoteTransfer { id: 0, chain_id: 1 }; + let result = ArgoBridge::finalize_inbound_transfer( + RuntimeOrigin::signed(account!(1)), + remote_transfer, + account!(2), + 1000, + ); + assert_err!(result, Error::::InsufficienBridgMintAllowance); + }); +} + +#[test] +fn pause_bridge_success() { + with_test_externalities(|| { + let parameters = BridgeConstraints { + operator_account: None, + pauser_accounts: Some(vec![account!(2)]), + bridging_fee: None, + thawn_duration: None, + remote_chains: None, + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + assert_ok!(ArgoBridge::pause_bridge(RuntimeOrigin::signed(account!(2)))); + }); +} + +#[test] +fn pause_bridge_with_unathorized_account() { + with_test_externalities(|| { + let parameters = BridgeConstraints { + operator_account: None, + pauser_accounts: Some(vec![account!(2)]), + bridging_fee: None, + thawn_duration: None, + remote_chains: None, + }; + assert_ok!(ArgoBridge::update_bridge_constrains( + RuntimeOrigin::root(), + parameters + )); + + let result = ArgoBridge::pause_bridge(RuntimeOrigin::signed(account!(1))); + assert_err!(result, Error::::NotPauserAccount); + }); +} + +#[test] +fn unpause_bridge_success() { + with_test_externalities(|| { + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: Some(vec![account!(2)]), + bridging_fee: None, + thawn_duration: Some(1), + remote_chains: None, + }; + ArgoBridge::update_bridge_constrains(RuntimeOrigin::root(), parameters).unwrap(); + + ArgoBridge::pause_bridge(RuntimeOrigin::signed(account!(2))).unwrap(); + + ArgoBridge::init_unpause_bridge(RuntimeOrigin::signed(account!(2))).unwrap(); + let current_block = >::block_number(); + assert_eq!( + ArgoBridge::status(), + BridgeStatus::Thawn { + thawn_ends_at: current_block + 1 + } + ); + increase_block_number_by(2); + assert_ok!(ArgoBridge::finish_unpause_bridge(RuntimeOrigin::signed( + account!(1) + ))); + }); +} + +#[test] +fn unpause_bridge_during_thawn() { + with_test_externalities(|| { + let thawn_duration: BlockNumber = 2; + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: Some(vec![account!(2)]), + bridging_fee: None, + thawn_duration: Some(thawn_duration), + remote_chains: None, + }; + ArgoBridge::update_bridge_constrains(RuntimeOrigin::root(), parameters).unwrap(); + + ArgoBridge::pause_bridge(RuntimeOrigin::signed(account!(2))).unwrap(); + + ArgoBridge::init_unpause_bridge(RuntimeOrigin::signed(account!(2))).unwrap(); + let current_block = >::block_number(); + assert_eq!( + ArgoBridge::status(), + BridgeStatus::Thawn { + thawn_ends_at: current_block + thawn_duration + } + ); + increase_block_number_by(1); + let result = ArgoBridge::finish_unpause_bridge(RuntimeOrigin::signed(account!(1))); + assert_err!(result, Error::::ThawnNotFinished); + }); +} + +#[test] +fn init_unpause_bridge_with_unathorized_account() { + with_test_externalities(|| { + let parameters = BridgeConstraints { + operator_account: Some(account!(1)), + pauser_accounts: Some(vec![account!(2)]), + bridging_fee: None, + thawn_duration: None, + remote_chains: None, + }; + ArgoBridge::update_bridge_constrains(RuntimeOrigin::root(), parameters).unwrap(); + + ArgoBridge::pause_bridge(RuntimeOrigin::signed(account!(2))).unwrap(); + + let result = ArgoBridge::init_unpause_bridge(RuntimeOrigin::signed(account!(1))); + assert_err!(result, Error::::NotPauserAccount); + }); +} diff --git a/runtime-modules/argo-bridge/src/types.rs b/runtime-modules/argo-bridge/src/types.rs new file mode 100644 index 0000000000..3b21d47c04 --- /dev/null +++ b/runtime-modules/argo-bridge/src/types.rs @@ -0,0 +1,61 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{storage::bounded_vec::BoundedVec, traits::ConstU32}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +use sp_std::vec::Vec; + +// Balance type alias +pub type BalanceOf = ::Balance; + +pub type ChainId = u32; +pub type TransferId = u64; + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] +pub struct BridgeConstraints { + pub operator_account: Option, + pub pauser_accounts: Option>, + pub bridging_fee: Option, + pub thawn_duration: Option, + pub remote_chains: Option>>, +} + +pub type BridgeConstraintsOf = BridgeConstraints< + ::AccountId, + BalanceOf, + ::BlockNumber, +>; + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] +pub struct RemoteAccount { + // Ethereum addresses only need 20 bytes but we use 32 bytes to enable compatibility with other chains + // When using Ethereum addresses, the last 12 bytes should be zero + pub account: [u8; 32], + pub chain_id: ChainId, +} + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] +pub struct RemoteTransfer { + pub id: TransferId, + pub chain_id: ChainId, +} + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] +pub enum BridgeStatus { + Active, + Paused, + Thawn { thawn_ends_at: BlockNumber }, +} + +impl Default for BridgeStatus { + fn default() -> Self { + BridgeStatus::Thawn { + thawn_ends_at: Default::default(), + } + } +} diff --git a/runtime-modules/argo-bridge/src/weights.rs b/runtime-modules/argo-bridge/src/weights.rs new file mode 100644 index 0000000000..41ab26ca9e --- /dev/null +++ b/runtime-modules/argo-bridge/src/weights.rs @@ -0,0 +1,69 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(unused_variables)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for project_token. +pub trait WeightInfo { + fn request_outbound_transfer() -> Weight; + + fn finalize_inbound_transfer() -> Weight; + + fn pause_bridge() -> Weight; + + fn init_unpause_bridge() -> Weight; + + fn finish_unpause_bridge() -> Weight; + + fn update_bridge_constrains() -> Weight; +} + +/// Weights for project_token using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + + fn request_outbound_transfer() -> Weight + { + Weight::from_parts(10_506_000, 0u64) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn finalize_inbound_transfer() -> Weight + { + Weight::from_parts(10_506_000, 0u64) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn pause_bridge() -> Weight + { + Weight::from_parts(10_506_000, 0u64) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn init_unpause_bridge() -> Weight + { + Weight::from_parts(10_506_000, 0u64) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn finish_unpause_bridge() -> Weight + { + Weight::from_parts(10_506_000, 0u64) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn update_bridge_constrains() -> Weight + { + Weight::from_parts(10_506_000, 0u64) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} \ No newline at end of file diff --git a/runtime-modules/proposals/codex/Cargo.toml b/runtime-modules/proposals/codex/Cargo.toml index 5f311d7eea..2346f4a5a4 100644 --- a/runtime-modules/proposals/codex/Cargo.toml +++ b/runtime-modules/proposals/codex/Cargo.toml @@ -30,6 +30,7 @@ content = { package = 'pallet-content', default-features = false, path = '../../ balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9', optional = true } council = { package = 'pallet-council', default-features = false, path = '../../council' } token = { package = 'pallet-project-token', default-features = false, path = '../../project-token' } +argo-bridge = { package = 'pallet-argo-bridge', default-features = false, path = '../../argo-bridge' } # Benchmarking dependencies frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9', optional = true } @@ -76,6 +77,7 @@ std = [ 'membership/std', 'content/std', 'token/std', + 'argo-bridge/std', 'storage/std', 'staking/std', 'scale-info/std', diff --git a/runtime-modules/proposals/codex/src/benchmarking.rs b/runtime-modules/proposals/codex/src/benchmarking.rs index ae5568af54..b91347c646 100644 --- a/runtime-modules/proposals/codex/src/benchmarking.rs +++ b/runtime-modules/proposals/codex/src/benchmarking.rs @@ -957,6 +957,38 @@ benchmarks! { ); } + create_proposal_argo_bridge_constraints { + let t in 1 .. to_kb(T::TitleMaxLength::get()); + let d in 1 .. to_kb(T::DescriptionMaxLength::get()); + + let (account_id, member_id, general_proposal_paramters) = + create_proposal_parameters::(t, d); + + // let member_id = Membership::::members_created(); + + let proposal_details = ProposalDetails::UpdateArgoBridgeConstraints( + argo_bridge::types::BridgeConstraints { + operator_account: Some(account::("operator", 0, SEED)), + pauser_accounts: Some(vec![account::("pauser", 0, SEED), account::("pauser", 1, SEED)] ), + bridging_fee: Some(100u32.into()), + thawn_duration: None, + remote_chains: None + } + ); + }: create_proposal( + RawOrigin::Signed(account_id.clone()), + general_proposal_paramters.clone(), + proposal_details.clone() + ) + verify { + create_proposal_verify::( + account_id, + member_id, + general_proposal_paramters, + proposal_details + ); + } + create_proposal_set_era_payout_damping_factor { let t in 1 .. to_kb(T::TitleMaxLength::get()); let d in 1 .. to_kb(T::DescriptionMaxLength::get()); diff --git a/runtime-modules/proposals/codex/src/lib.rs b/runtime-modules/proposals/codex/src/lib.rs index 733f08ef7e..e5b4ae3bda 100644 --- a/runtime-modules/proposals/codex/src/lib.rs +++ b/runtime-modules/proposals/codex/src/lib.rs @@ -287,6 +287,9 @@ pub trait Config: ProposalParameters>, >; + /// `Update pallet project token` proposal parameters + type UpdateArgoBridgeConstraints: Get>>; + /// `Set Era Payout Damping Factor` proposal parameters type SetEraPayoutDampingFactorProposalParameters: Get< ProposalParameters>, @@ -535,6 +538,10 @@ decl_module! { const UpdateTokenPalletTokenConstraints: ProposalParameters> = T::UpdateTokenPalletTokenConstraints::get(); + /// Set Argo Bridge Constraints + const UpdateArgoBridgeConstraints: + ProposalParameters> = T::UpdateArgoBridgeConstraints::get(); + /// Era payout damping factor const SetEraPayoutDampingFactorProposalParameters: ProposalParameters> = T::SetEraPayoutDampingFactorProposalParameters::get(); @@ -919,6 +926,9 @@ impl Module { ProposalDetails::UpdateTokenPalletTokenConstraints(..) => { // Note: No checks for this proposal for now } + ProposalDetails::UpdateArgoBridgeConstraints(..) => { + // Note: No checks for this proposal for now + } ProposalDetails::SetEraPayoutDampingFactor(..) => { // Note: No checks for this proposal for now } @@ -998,6 +1008,9 @@ impl Module { ProposalDetails::UpdateTokenPalletTokenConstraints(..) => { T::UpdateTokenPalletTokenConstraints::get() } + ProposalDetails::UpdateArgoBridgeConstraints(..) => { + T::UpdateArgoBridgeConstraints::get() + } ProposalDetails::SetEraPayoutDampingFactor(..) => { T::SetEraPayoutDampingFactorProposalParameters::get() } @@ -1180,6 +1193,12 @@ impl Module { to_kb(description_length.saturated_into()), ) } + ProposalDetails::UpdateArgoBridgeConstraints(..) => { + WeightInfoCodex::::create_proposal_update_argo_bridge_constraints( + to_kb(title_length.saturated_into()), + to_kb(description_length.saturated_into()), + ) + } ProposalDetails::SetEraPayoutDampingFactor(..) => { WeightInfoCodex::::create_proposal_set_era_payout_damping_factor( to_kb(title_length.saturated_into()), diff --git a/runtime-modules/proposals/codex/src/tests/mock.rs b/runtime-modules/proposals/codex/src/tests/mock.rs index 73ada48810..b880aa09b1 100644 --- a/runtime-modules/proposals/codex/src/tests/mock.rs +++ b/runtime-modules/proposals/codex/src/tests/mock.rs @@ -786,6 +786,7 @@ impl crate::Config for Test { type SetMaxValidatorCountProposalMaxValidators = SetMaxValidatorCountProposalMaxValidators; type SetPalletFozenStatusProposalParameters = DefaultProposalParameters; type UpdateTokenPalletTokenConstraints = DefaultProposalParameters; + type UpdateArgoBridgeConstraints = DefaultProposalParameters; type SetEraPayoutDampingFactorProposalParameters = DefaultProposalParameters; type DecreaseCouncilBudgetProposalParameters = DefaultProposalParameters; } diff --git a/runtime-modules/proposals/codex/src/types.rs b/runtime-modules/proposals/codex/src/types.rs index bcbc013ee4..6eaf20c34e 100644 --- a/runtime-modules/proposals/codex/src/types.rs +++ b/runtime-modules/proposals/codex/src/types.rs @@ -34,6 +34,7 @@ pub type ProposalDetailsOf = ProposalDetails< ::ProposalId, content::UpdateChannelPayoutsParameters, token::TokenConstraintsOf, + argo_bridge::types::BridgeConstraintsOf, >; /// Proposal details provide voters the information required for the perceived voting. @@ -48,6 +49,7 @@ pub enum ProposalDetails< ProposalId, UpdateChannelPayoutsParameters, TokenConstraints, + ArgoBridgeConstraints, > { /// The signal of the `Signal` proposal Signal(Vec), @@ -129,6 +131,9 @@ pub enum ProposalDetails< /// Update token constraints UpdateTokenPalletTokenConstraints(TokenConstraints), + /// Update Argo Bridge contraints + UpdateArgoBridgeConstraints(ArgoBridgeConstraints), + /// `SetEraPayoutDampingFactor` proposal SetEraPayoutDampingFactor(Percent), @@ -145,6 +150,7 @@ impl< ProposalId, UpdateChannelPayoutsParameters, TokenConstraints, + ArgoBridgeConstraints, > Default for ProposalDetails< Balance, @@ -155,6 +161,7 @@ impl< ProposalId, UpdateChannelPayoutsParameters, TokenConstraints, + ArgoBridgeConstraints, > { fn default() -> Self { diff --git a/runtime-modules/proposals/codex/src/weights.rs b/runtime-modules/proposals/codex/src/weights.rs index e697e620fb..31deaf1a4f 100644 --- a/runtime-modules/proposals/codex/src/weights.rs +++ b/runtime-modules/proposals/codex/src/weights.rs @@ -69,6 +69,7 @@ pub trait WeightInfo { fn create_proposal_update_channel_payouts(_t: u32, _d: u32, _i: u32, ) -> Weight; fn create_proposal_freeze_pallet(_t: u32, _d: u32, ) -> Weight; fn create_proposal_update_token_pallet_token_constraints(_t: u32, _d: u32, ) -> Weight; + fn create_proposal_update_argo_bridge_constraints(_t: u32, _d: u32, ) -> Weight; fn create_proposal_set_era_payout_damping_factor(_t: u32, _d: u32, ) -> Weight; fn create_proposal_decrease_council_budget(_t: u32, _d: u32, ) -> Weight; } @@ -1060,6 +1061,22 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(9_u64)) } + + fn create_proposal_update_argo_bridge_constraints(t: u32, d: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `651` + // Estimated: `19940` + // Minimum execution time: 103_350 nanoseconds. + Weight::from_parts(82_522_075, 0u64) + .saturating_add(Weight::from_parts(0, 19940)) + // Standard Error: 12_486 + .saturating_add(Weight::from_parts(1_112_896, 0u64).saturating_mul(t.into())) + // Standard Error: 12_486 + .saturating_add(Weight::from_parts(1_256_380, 0u64).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) + } + // Storage: Membership MembershipById (r:1 w:0) // Proof: Membership MembershipById (max_values: None, max_size: Some(125), added: 2600, mode: MaxEncodedLen) // Storage: ProposalEngine ActiveProposalCount (r:1 w:1) @@ -1215,6 +1232,9 @@ impl WeightInfo for () { fn create_proposal_update_token_pallet_token_constraints(t: u32, d: u32, ) -> Weight { Weight::from_parts(0, 0) } + fn create_proposal_update_argo_bridge_constraints(t: u32, d: u32, ) -> Weight { + Weight::from_parts(0, 0) + } fn create_proposal_set_era_payout_damping_factor(t: u32, d: u32, ) -> Weight { Weight::from_parts(0, 0) } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 7dca078144..fd8fa7b622 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -99,6 +99,7 @@ bounty = { package = 'pallet-bounty', default-features = false, path = '../runti content = { package = 'pallet-content', default-features = false, path = '../runtime-modules/content' } joystream-utility = { package = 'pallet-joystream-utility', default-features = false, path = '../runtime-modules/utility' } project-token = { package = 'pallet-project-token', default-features = false, path = '../runtime-modules/project-token' } +argo-bridge = { package = 'pallet-argo-bridge', default-features = false, path = '../runtime-modules/argo-bridge' } [dev-dependencies] sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/joystream/substrate.git', rev = '1d0eefca86ef31b9e7727df01a6ed23ad65491e9' } @@ -189,6 +190,7 @@ std = [ 'joystream-utility/std', 'content/std', 'project-token/std', + 'argo-bridge/std', 'log/std', ] runtime-benchmarks = [ @@ -233,6 +235,7 @@ runtime-benchmarks = [ 'storage/runtime-benchmarks', 'content/runtime-benchmarks', "project-token/runtime-benchmarks", + "argo-bridge/runtime-benchmarks" ] # Configuration suitable for staging networks and playground @@ -290,4 +293,5 @@ try-runtime = [ 'joystream-utility/try-runtime', 'content/try-runtime', 'project-token/try-runtime', + 'argo-bridge/try-runtime' ] diff --git a/runtime/src/integration/proposals/proposal_encoder.rs b/runtime/src/integration/proposals/proposal_encoder.rs index 1b76c6719b..1c772f2d51 100644 --- a/runtime/src/integration/proposals/proposal_encoder.rs +++ b/runtime/src/integration/proposals/proposal_encoder.rs @@ -171,6 +171,9 @@ impl ProposalEncoder for ExtrinsicProposalEncoder { parameters, }) } + ProposalDetails::UpdateArgoBridgeConstraints(parameters) => { + RuntimeCall::ArgoBridge(argo_bridge::Call::update_bridge_constrains { parameters }) + } ProposalDetails::SetPalletFozenStatus(freeze, pallet) => match pallet { FreezablePallet::ProjectToken => { RuntimeCall::ProjectToken(project_token::Call::set_frozen_status { freeze }) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8cc543791d..cd5f35bdac 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,6 +139,8 @@ pub use content; pub use content::LimitPerPeriod; pub use content::MaxNumber; +pub use argo_bridge; + /// This runtime version. #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { @@ -935,6 +937,18 @@ impl project_token::Config for Runtime { type WeightInfo = project_token::weights::SubstrateWeight; } +parameter_types! { + pub const MaxPauserAccounts: u32 = 10; + pub const DefaultBridgingFee: Balance = dollars!(1_000); +} + +impl argo_bridge::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxPauserAccounts = MaxPauserAccounts; + type WeightInfo = argo_bridge::weights::SubstrateWeight; + type DefaultBridgingFee = DefaultBridgingFee; +} + // The referendum instance alias. pub type ReferendumInstance = referendum::Instance1; pub type ReferendumModule = referendum::Module; @@ -1717,6 +1731,7 @@ impl proposals_codex::Config for Runtime { type FundingRequestProposalMaxAccounts = FundingRequestProposalMaxAccounts; type SetMaxValidatorCountProposalMaxValidators = SetMaxValidatorCountProposalMaxValidators; type UpdateTokenPalletTokenConstraints = UpdateTokenPalletTokenConstraints; + type UpdateArgoBridgeConstraints = UpdateArgoBridgeConstraints; type SetPalletFozenStatusProposalParameters = SetPalletFozenStatusProposalParameters; type SetEraPayoutDampingFactorProposalParameters = SetEraPayoutDampingFactorProposalParameters; type WeightInfo = proposals_codex::weights::SubstrateWeight; @@ -1979,6 +1994,7 @@ construct_runtime!( Content: content::{Pallet, Call, Storage, Event, Config}, Storage: storage::{Pallet, Call, Storage, Event, Config}, ProjectToken: project_token::{Pallet, Call, Storage, Event, Config}, + ArgoBridge: argo_bridge::{Pallet, Call, Storage, Event, Config}, // --- Proposals ProposalsEngine: proposals_engine::{Pallet, Call, Storage, Event}, ProposalsDiscussion: proposals_discussion::{Pallet, Call, Storage, Event, Config}, diff --git a/runtime/src/proposals_configuration/defaults.rs b/runtime/src/proposals_configuration/defaults.rs index 6b942f1ffd..44e69d3491 100644 --- a/runtime/src/proposals_configuration/defaults.rs +++ b/runtime/src/proposals_configuration/defaults.rs @@ -358,6 +358,20 @@ pub(crate) fn update_token_pallet_token_governance_parameters( } } +// Proposal parameters for the 'Update Argo Bridge Parameters' proposal +pub(crate) fn update_argo_bridge_parameters() -> ProposalParameters { + ProposalParameters { + voting_period: days!(7), + grace_period: days!(1), + approval_quorum_percentage: TWO_OUT_OF_THREE, + approval_threshold_percentage: ALL, + slashing_quorum_percentage: ALL, + slashing_threshold_percentage: ALL, + required_stake: Some(dollars!(100)), + constitutionality: 1, + } +} + // Proposal parameters for the 'Freeze Pallet' proposal pub(crate) fn freeze_pallet_proposal() -> ProposalParameters { ProposalParameters { diff --git a/runtime/src/proposals_configuration/mod.rs b/runtime/src/proposals_configuration/mod.rs index 9f71890b28..692d46e169 100644 --- a/runtime/src/proposals_configuration/mod.rs +++ b/runtime/src/proposals_configuration/mod.rs @@ -113,6 +113,9 @@ parameter_types! { pub UpdateTokenPalletTokenConstraints: ProposalParameters = update_token_pallet_token_governance_parameters(); + pub UpdateArgoBridgeConstraints: ProposalParameters = + update_argo_bridge_parameters(); + pub SetEraPayoutDampingFactorProposalParameters: ProposalParameters = set_era_payout_damping_factor(); diff --git a/runtime/src/proposals_configuration/playground.rs b/runtime/src/proposals_configuration/playground.rs index aaffcd1b04..4de8efb2ba 100644 --- a/runtime/src/proposals_configuration/playground.rs +++ b/runtime/src/proposals_configuration/playground.rs @@ -374,6 +374,20 @@ pub(crate) fn update_token_pallet_token_governance_parameters( } } +// Proposal parameters for the 'Update Argo Bridge Parameters' proposal +pub(crate) fn update_argo_bridge_parameters() -> ProposalParameters { + ProposalParameters { + voting_period: 200, + grace_period: 0, + approval_quorum_percentage: 60, + approval_threshold_percentage: 75, + slashing_quorum_percentage: 60, + slashing_threshold_percentage: 80, + required_stake: Some(dollars!(50)), + constitutionality: 1, + } +} + // Proposal parameters for the 'Decrease Council Budget' proposal pub(crate) fn decrease_council_budget() -> ProposalParameters { ProposalParameters { diff --git a/runtime/src/proposals_configuration/staging.rs b/runtime/src/proposals_configuration/staging.rs index 5163a80b03..54add58753 100644 --- a/runtime/src/proposals_configuration/staging.rs +++ b/runtime/src/proposals_configuration/staging.rs @@ -371,6 +371,20 @@ pub(crate) fn update_token_pallet_token_governance_parameters( } } +// Proposal parameters for the 'Update Argo Bridge Parameters' proposal +pub(crate) fn update_argo_bridge_parameters() -> ProposalParameters { + ProposalParameters { + voting_period: minutes!(20), + grace_period: 0, + approval_quorum_percentage: TWO_OUT_OF_THREE, + approval_threshold_percentage: TWO_OUT_OF_THREE, + slashing_quorum_percentage: ALL, + slashing_threshold_percentage: ALL, + required_stake: Some(dollars!(50)), + constitutionality: 1, + } +} + // Proposal parameters for the 'Set Era Payout Damping Factor' proposal pub(crate) fn set_era_payout_damping_factor() -> ProposalParameters { ProposalParameters { diff --git a/runtime/src/proposals_configuration/testing.rs b/runtime/src/proposals_configuration/testing.rs index 2e9849e974..268ae1585b 100644 --- a/runtime/src/proposals_configuration/testing.rs +++ b/runtime/src/proposals_configuration/testing.rs @@ -361,6 +361,20 @@ pub(crate) fn update_token_pallet_token_governance_parameters( } } +// Proposal parameters for the 'Update Argo Bridge Parameters' proposal +pub(crate) fn update_argo_bridge_parameters() -> ProposalParameters { + ProposalParameters { + voting_period: 30, + grace_period: 0, + approval_quorum_percentage: 60, + approval_threshold_percentage: 75, + slashing_quorum_percentage: 60, + slashing_threshold_percentage: 80, + required_stake: Some(currency::DOLLARS.saturating_mul(50)), + constitutionality: 1, + } +} + // Proposal parameters for the 'Set Era Payout Damping Factor' proposal pub(crate) fn set_era_payout_damping_factor() -> ProposalParameters { ProposalParameters {