diff --git a/state-chain/cf-integration-tests/src/account.rs b/state-chain/cf-integration-tests/src/account.rs index 9716a84cd5..bb88e7532d 100644 --- a/state-chain/cf-integration-tests/src/account.rs +++ b/state-chain/cf-integration-tests/src/account.rs @@ -11,7 +11,7 @@ use state_chain_runtime::{Reputation, Runtime, Validator}; #[test] fn account_deletion_removes_relevant_storage_items() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { let genesis_nodes = Validator::current_authorities(); // Create a single backup node which we will use to test deletion diff --git a/state-chain/cf-integration-tests/src/authorities.rs b/state-chain/cf-integration-tests/src/authorities.rs index c62004ddd0..3948b6f11a 100644 --- a/state-chain/cf-integration-tests/src/authorities.rs +++ b/state-chain/cf-integration-tests/src/authorities.rs @@ -58,7 +58,7 @@ pub fn fund_authorities_and_join_auction( fn authority_rotates_with_correct_sequence() { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -154,7 +154,7 @@ fn authorities_earn_rewards_for_authoring_blocks() { // Reduce our validating set and hence the number of nodes we need to have a backup // set const MAX_AUTHORITIES: AuthorityCount = 3; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -198,7 +198,7 @@ fn genesis_nodes_rotated_out_accumulate_rewards_correctly() { // Reduce our validating set and hence the number of nodes we need to have a backup // set const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -263,7 +263,7 @@ fn genesis_nodes_rotated_out_accumulate_rewards_correctly() { fn authority_rotation_can_succeed_after_aborted_by_safe_mode() { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -318,7 +318,7 @@ fn authority_rotation_cannot_be_aborted_after_key_handover_and_completes_even_on { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -361,7 +361,7 @@ fn authority_rotation_cannot_be_aborted_after_key_handover_and_completes_even_on fn authority_rotation_can_recover_after_keygen_fails() { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -409,7 +409,7 @@ fn authority_rotation_can_recover_after_keygen_fails() { fn authority_rotation_can_recover_after_key_handover_fails() { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -477,7 +477,7 @@ fn authority_rotation_can_recover_after_key_handover_fails() { fn can_move_through_multiple_epochs() { const EPOCH_BLOCKS: u32 = 100; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() diff --git a/state-chain/cf-integration-tests/src/broadcasting.rs b/state-chain/cf-integration-tests/src/broadcasting.rs index 33855a7821..e016608e9a 100644 --- a/state-chain/cf-integration-tests/src/broadcasting.rs +++ b/state-chain/cf-integration-tests/src/broadcasting.rs @@ -14,7 +14,7 @@ use state_chain_runtime::{ fn bitcoin_broadcast_delay_works() { const EPOCH_BLOCKS: u32 = 200; const MAX_AUTHORITIES: AuthorityCount = 150; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() diff --git a/state-chain/cf-integration-tests/src/funding.rs b/state-chain/cf-integration-tests/src/funding.rs index 35de2c5b58..cd1ff5ca56 100644 --- a/state-chain/cf-integration-tests/src/funding.rs +++ b/state-chain/cf-integration-tests/src/funding.rs @@ -24,7 +24,7 @@ use state_chain_runtime::{ fn cannot_redeem_funds_out_of_redemption_period() { const EPOCH_BLOCKS: u32 = 100; const MAX_AUTHORITIES: AuthorityCount = 3; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -124,7 +124,7 @@ fn cannot_redeem_funds_out_of_redemption_period() { #[test] fn funded_node_is_added_to_backups() { const EPOCH_BLOCKS: u32 = 10_000_000; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) // As we run a rotation at genesis we will need accounts to support // having 5 authorities as the default is 3 (Alice, Bob and Charlie) @@ -148,7 +148,7 @@ fn backup_reward_is_calculated_linearly() { const EPOCH_BLOCKS: u32 = 1_000; const MAX_AUTHORITIES: u32 = 10; const NUM_BACKUPS: u32 = 20; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -193,7 +193,7 @@ fn can_calculate_account_apy() { const EPOCH_BLOCKS: u32 = 1_000; const MAX_AUTHORITIES: u32 = 10; const NUM_BACKUPS: u32 = 20; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -245,7 +245,7 @@ fn apy_can_be_above_100_percent() { const EPOCH_BLOCKS: u32 = 1_000; const MAX_AUTHORITIES: u32 = 2; const NUM_BACKUPS: u32 = 2; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -280,7 +280,7 @@ fn backup_rewards_event_gets_emitted_on_heartbeat_interval() { const EPOCH_BLOCKS: u32 = 1_000; const NUM_BACKUPS: u32 = 20; const MAX_AUTHORITIES: u32 = 100; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .accounts( (0..MAX_AUTHORITIES as u8) diff --git a/state-chain/cf-integration-tests/src/genesis.rs b/state-chain/cf-integration-tests/src/genesis.rs index 38d3a52a73..7af4ce84ae 100644 --- a/state-chain/cf-integration-tests/src/genesis.rs +++ b/state-chain/cf-integration-tests/src/genesis.rs @@ -15,7 +15,7 @@ pub const GENESIS_BALANCE: FlipBalance = TOTAL_ISSUANCE / 100; const BLOCKS_PER_EPOCH: u32 = 1000; -pub fn default() -> ExtBuilder { +pub fn with_test_defaults() -> ExtBuilder { ExtBuilder::default() .accounts(vec![ (AccountId::from(ALICE), AccountRole::Validator, GENESIS_BALANCE), @@ -30,7 +30,7 @@ pub fn default() -> ExtBuilder { #[test] fn state_of_genesis_is_as_expected() { - default().build().execute_with(|| { + with_test_defaults().build().execute_with(|| { // Confirmation that we have our assumed state at block 1 assert_eq!(Flip::total_issuance(), TOTAL_ISSUANCE, "we have issued the total issuance"); diff --git a/state-chain/cf-integration-tests/src/governance.rs b/state-chain/cf-integration-tests/src/governance.rs index f66a47ecc6..8e7169da0d 100644 --- a/state-chain/cf-integration-tests/src/governance.rs +++ b/state-chain/cf-integration-tests/src/governance.rs @@ -6,7 +6,7 @@ use pallet_transaction_payment::OnChargeTransaction; #[test] // Governance is allowed to make free calls to governance gated extrinsics. fn governance_members_pay_no_fees_for_governance_extrinsics() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { let call: state_chain_runtime::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); let gov_call: state_chain_runtime::RuntimeCall = diff --git a/state-chain/cf-integration-tests/src/mock_runtime.rs b/state-chain/cf-integration-tests/src/mock_runtime.rs index f69048dd54..daed5f805a 100644 --- a/state-chain/cf-integration-tests/src/mock_runtime.rs +++ b/state-chain/cf-integration-tests/src/mock_runtime.rs @@ -77,6 +77,14 @@ impl ExtBuilder { self } + pub fn with_additional_accounts( + mut self, + accounts: &[(AccountId, AccountRole, FlipBalance)], + ) -> Self { + self.genesis_accounts.extend_from_slice(accounts); + self + } + pub fn root(mut self, root: AccountId) -> Self { self.root = Some(root); self @@ -161,7 +169,14 @@ impl ExtBuilder { genesis_backups: Default::default(), genesis_vanity_names: Default::default(), blocks_per_epoch: self.blocks_per_epoch, - bond: self.genesis_accounts.iter().map(|(.., amount)| *amount).min().unwrap(), + bond: self + .genesis_accounts + .iter() + .filter_map(|(.., role, amount)| { + matches!(role, AccountRole::Validator).then_some(*amount) + }) + .min() + .unwrap(), redemption_period_as_percentage: Percent::from_percent( REDEMPTION_PERIOD_AS_PERCENTAGE, ), diff --git a/state-chain/cf-integration-tests/src/network.rs b/state-chain/cf-integration-tests/src/network.rs index 61eb6d5604..9b6418ee46 100644 --- a/state-chain/cf-integration-tests/src/network.rs +++ b/state-chain/cf-integration-tests/src/network.rs @@ -2,6 +2,7 @@ use super::*; use crate::threshold_signing::{BtcThresholdSigner, DotThresholdSigner, EthThresholdSigner}; +use cf_chains::address::EncodedAddress; use cf_primitives::{AccountRole, BlockNumber, EpochIndex, FlipBalance, TxId, GENESIS_EPOCH}; use cf_test_utilities::assert_events_eq; use cf_traits::{AccountRoleRegistry, Chainflip, EpochInfo, KeyRotator}; @@ -17,8 +18,8 @@ use pallet_cf_funding::{MinimumFunding, RedemptionAmount}; use sp_consensus_aura::SlotDuration; use sp_std::collections::btree_set::BTreeSet; use state_chain_runtime::{ - AccountRoles, AllPalletsWithSystem, BitcoinInstance, PalletExecutionOrder, PolkadotInstance, - Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Validator, Weight, + AccountRoles, AllPalletsWithSystem, BitcoinInstance, LiquidityProvider, PalletExecutionOrder, + PolkadotInstance, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Validator, Weight, }; use std::{ cell::RefCell, @@ -736,10 +737,11 @@ pub fn fund_authorities_and_join_auction( (testnet, genesis_authorities, init_backup_nodes) } -// Helper function that registers account role for a new account. pub fn new_account(account_id: &AccountId, role: AccountRole) { - ::OnNewAccount::on_new_account(account_id); - System::inc_providers(account_id); + use cf_traits::Funding; + + Flip::credit_funds(account_id, FLIPPERINOS_PER_FLIP); + AccountRoles::on_new_account(account_id); assert_ok!(AccountRoles::register_account_role(account_id, role)); assert_events_eq!( Runtime, @@ -748,5 +750,21 @@ pub fn new_account(account_id: &AccountId, role: AccountRole) { role, }) ); + if role == AccountRole::LiquidityProvider { + register_refund_addresses(account_id); + } System::reset_events(); } + +pub fn register_refund_addresses(account_id: &AccountId) { + for encoded_address in [ + EncodedAddress::Eth(Default::default()), + EncodedAddress::Dot(Default::default()), + EncodedAddress::Btc("bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw".as_bytes().to_vec()), + ] { + assert_ok!(LiquidityProvider::register_liquidity_refund_address( + RuntimeOrigin::signed(account_id.clone()), + encoded_address + )); + } +} diff --git a/state-chain/cf-integration-tests/src/new_epoch.rs b/state-chain/cf-integration-tests/src/new_epoch.rs index 65146dbb78..27084023b9 100644 --- a/state-chain/cf-integration-tests/src/new_epoch.rs +++ b/state-chain/cf-integration-tests/src/new_epoch.rs @@ -8,7 +8,7 @@ use state_chain_runtime::Validator; #[test] fn auction_repeats_after_failure_because_of_liveness() { const EPOCH_BLOCKS: BlockNumber = 1000; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) // As we run a rotation at genesis we will need accounts to support // having 5 authorities as the default is 3 (Alice, Bob and Charlie) @@ -96,7 +96,7 @@ fn auction_repeats_after_failure_because_of_liveness() { fn epoch_rotates() { const EPOCH_BLOCKS: BlockNumber = 1000; const MAX_SET_SIZE: AuthorityCount = 5; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .min_authorities(MAX_SET_SIZE) .build() diff --git a/state-chain/cf-integration-tests/src/signer_nomination.rs b/state-chain/cf-integration-tests/src/signer_nomination.rs index 03a92ec901..a050ae4de0 100644 --- a/state-chain/cf-integration-tests/src/signer_nomination.rs +++ b/state-chain/cf-integration-tests/src/signer_nomination.rs @@ -11,7 +11,7 @@ type RuntimeThresholdSignerNomination = #[test] fn threshold_signer_nomination_respects_epoch() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { let genesis_authorities = Validator::current_authorities(); let genesis_epoch = Validator::epoch_index(); @@ -76,7 +76,7 @@ fn test_not_nominated_for_offence(penalise: F) { #[test] fn nodes_who_failed_to_sign_excluded_from_threshold_nomination() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { test_not_nominated_for_offence(|node_id| { Reputation::report(PalletOffence::ParticipateSigningFailed, node_id) }); diff --git a/state-chain/cf-integration-tests/src/swapping.rs b/state-chain/cf-integration-tests/src/swapping.rs index a74bce5ccb..c1b3621ab6 100644 --- a/state-chain/cf-integration-tests/src/swapping.rs +++ b/state-chain/cf-integration-tests/src/swapping.rs @@ -1,9 +1,11 @@ //! Contains tests related to liquidity, pools and swapping +use std::vec; + use crate::{ genesis, network::{ - fund_authorities_and_join_auction, new_account, setup_account_and_peer_mapping, Cli, - Network, + fund_authorities_and_join_auction, new_account, register_refund_addresses, + setup_account_and_peer_mapping, Cli, Network, }, witness_call, }; @@ -21,7 +23,8 @@ use cf_chains::{ TransactionBuilder, TransferAssetParams, }; use cf_primitives::{ - AccountId, AccountRole, Asset, AssetAmount, AuthorityCount, GENESIS_EPOCH, STABLE_ASSET, + AccountId, AccountRole, Asset, AssetAmount, AuthorityCount, FLIPPERINOS_PER_FLIP, + GENESIS_EPOCH, STABLE_ASSET, }; use cf_test_utilities::{assert_events_eq, assert_events_match}; use cf_traits::{Chainflip, EpochInfo, LpBalanceApi}; @@ -71,19 +74,6 @@ fn new_pool(unstable_asset: Asset, fee_hundredth_pips: u32, initial_price: Price System::reset_events(); } -fn register_refund_addresses(account_id: &AccountId) { - for encoded_address in [ - EncodedAddress::Eth(Default::default()), - EncodedAddress::Dot(Default::default()), - EncodedAddress::Btc("bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw".as_bytes().to_vec()), - ] { - assert_ok!(LiquidityProvider::register_liquidity_refund_address( - RuntimeOrigin::signed(account_id.clone()), - encoded_address - )); - } -} - fn credit_account(account_id: &AccountId, asset: Asset, amount: AssetAmount) { let original_amount = pallet_cf_lp::FreeBalances::::get(account_id, asset).unwrap_or_default(); @@ -193,8 +183,6 @@ fn set_limit_order( fn setup_pool_and_accounts(assets: Vec) { new_account(&DORIS, AccountRole::LiquidityProvider); - register_refund_addresses(&DORIS); - new_account(&ZION, AccountRole::Broker); for asset in assets { @@ -215,12 +203,17 @@ fn get_asset_balance(who: &AccountId, asset: Asset) -> u128 { #[test] fn basic_pool_setup_provision_and_swap() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults() + .with_additional_accounts(&[ + (DORIS, AccountRole::LiquidityProvider, 5 * FLIPPERINOS_PER_FLIP), + (ZION, AccountRole::Broker, 5 * FLIPPERINOS_PER_FLIP), + ]) + .build() + .execute_with(|| { new_pool(Asset::Eth, 0u32, price_at_tick(0).unwrap()); new_pool(Asset::Flip, 0u32, price_at_tick(0).unwrap()); - - new_account(&DORIS, AccountRole::LiquidityProvider); register_refund_addresses(&DORIS); + credit_account(&DORIS, Asset::Eth, 1_000_000); credit_account(&DORIS, Asset::Flip, 1_000_000); credit_account(&DORIS, Asset::Usdc, 1_000_000); @@ -231,8 +224,6 @@ fn basic_pool_setup_provision_and_swap() { set_limit_order(&DORIS, Asset::Flip, Asset::Usdc, 0, Some(0), 500_000); set_range_order(&DORIS, Asset::Flip, Asset::Usdc, 0, Some(-10..10), 1_000_000); - new_account(&ZION, AccountRole::Broker); - let usdc_balance_before = get_asset_balance(&DORIS, Asset::Usdc); assert_ok!(Swapping::request_swap_deposit_address( @@ -326,7 +317,7 @@ fn basic_pool_setup_provision_and_swap() { #[test] fn can_process_ccm_via_swap_deposit_address() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { // Setup pool and liquidity setup_pool_and_accounts(vec![Asset::Eth, Asset::Flip]); @@ -434,7 +425,7 @@ fn can_process_ccm_via_swap_deposit_address() { #[test] fn can_process_ccm_via_direct_deposit() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { setup_pool_and_accounts(vec![Asset::Eth, Asset::Flip]); let gas_budget = 100; @@ -526,7 +517,7 @@ fn can_process_ccm_via_direct_deposit() { #[test] fn failed_swaps_are_rolled_back() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { setup_pool_and_accounts(vec![Asset::Eth, Asset::Btc]); // Get current pool's liquidity @@ -685,7 +676,7 @@ fn failed_swaps_are_rolled_back() { #[test] fn ethereum_ccm_can_calculate_gas_limits() { - super::genesis::default().build().execute_with(|| { + super::genesis::with_test_defaults().build().execute_with(|| { let chain_state = ChainState:: { block_height: 1, tracked_data: EthereumTrackedData { @@ -754,7 +745,7 @@ fn ethereum_ccm_can_calculate_gas_limits() { fn can_resign_failed_ccm() { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() @@ -883,7 +874,7 @@ fn can_resign_failed_ccm() { fn can_handle_failed_vault_transfer() { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 10; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() diff --git a/state-chain/cf-integration-tests/src/witnessing.rs b/state-chain/cf-integration-tests/src/witnessing.rs index 5f4cdb41d7..cd8b85a523 100644 --- a/state-chain/cf-integration-tests/src/witnessing.rs +++ b/state-chain/cf-integration-tests/src/witnessing.rs @@ -18,7 +18,7 @@ use pallet_cf_witnesser::{CallHash, CallHashExecuted, WitnessDeadline}; fn can_punish_failed_witnesser() { const EPOCH_BLOCKS: u32 = 1000; const MAX_AUTHORITIES: AuthorityCount = 50; - super::genesis::default() + super::genesis::with_test_defaults() .blocks_per_epoch(EPOCH_BLOCKS) .max_authorities(MAX_AUTHORITIES) .build() diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index 3d43a7124e..0558a1bf06 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -206,6 +206,7 @@ pub struct IngressEgressEnvironment { pub egress_fees: any::AssetMap>, pub witness_safety_margins: HashMap>, pub egress_dust_limits: any::AssetMap, + pub channel_opening_fees: HashMap, } #[derive(Serialize, Deserialize)] @@ -965,12 +966,17 @@ where let hash = self.unwrap_or_best(at); let mut witness_safety_margins = HashMap::new(); + let mut channel_opening_fees = HashMap::new(); for chain in ForeignChain::iter() { witness_safety_margins.insert( chain, runtime_api.cf_witness_safety_margin(hash, chain).map_err(to_rpc_error)?, ); + channel_opening_fees.insert( + chain, + runtime_api.cf_channel_opening_fee(hash, chain).map_err(to_rpc_error)?.into(), + ); } Ok(IngressEgressEnvironment { @@ -999,6 +1005,7 @@ where .map_err(to_rpc_error) .map(Into::into) })?, + channel_opening_fees, }) } @@ -1413,6 +1420,11 @@ mod test { btc: btc::AssetMap { btc: 0u32.into() }, dot: dot::AssetMap { dot: 0u32.into() }, }, + channel_opening_fees: HashMap::from([ + (ForeignChain::Bitcoin, 0u32.into()), + (ForeignChain::Ethereum, 1000u32.into()), + (ForeignChain::Polkadot, 1000u32.into()), + ]), }, funding: FundingEnvironment { redemption_tax: 0u32.into(), diff --git a/state-chain/custom-rpc/src/snapshots/custom_rpc__test__environment_serialization.snap b/state-chain/custom-rpc/src/snapshots/custom_rpc__test__environment_serialization.snap index 4462b9545b..08eff59d41 100644 --- a/state-chain/custom-rpc/src/snapshots/custom_rpc__test__environment_serialization.snap +++ b/state-chain/custom-rpc/src/snapshots/custom_rpc__test__environment_serialization.snap @@ -3,4 +3,4 @@ source: state-chain/custom-rpc/src/lib.rs assertion_line: 1448 expression: "serde_json::to_value(env).unwrap()" --- -{"funding":{"minimum_funding_amount":0,"redemption_tax":0},"ingress_egress":{"egress_dust_limits":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffffffffffffffffffff","USDC":"0x7ffffffffffffffe"},"Polkadot":{"DOT":0}},"egress_fees":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffffffffffffffffffff","USDC":null},"Polkadot":{"DOT":"0x7ffffffffffffffe"}},"ingress_fees":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffffffffffffffffffff","USDC":null},"Polkadot":{"DOT":"0x7ffffffffffffffe"}},"minimum_deposit_amounts":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffff","USDC":"0x7ffffffffffffffe"},"Polkadot":{"DOT":0}},"witness_safety_margins":{"Bitcoin":3,"Ethereum":3,"Polkadot":null}},"pools":{"fees":{"Ethereum":{"FLIP":{"limit_order_fee_hundredth_pips":0,"quote_asset":{"asset":"USDC","chain":"Ethereum"},"range_order_fee_hundredth_pips":100}}}},"swapping":{"maximum_swap_amounts":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":null,"USDC":"0x7ffffffffffffffe"},"Polkadot":{"DOT":null}},"network_fee_hundredth_pips":1000000}} +{"funding":{"minimum_funding_amount":0,"redemption_tax":0},"ingress_egress":{"channel_opening_fees":{"Bitcoin":0,"Ethereum":1000,"Polkadot":1000},"egress_dust_limits":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffffffffffffffffffff","USDC":"0x7ffffffffffffffe"},"Polkadot":{"DOT":0}},"egress_fees":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffffffffffffffffffff","USDC":null},"Polkadot":{"DOT":"0x7ffffffffffffffe"}},"ingress_fees":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffffffffffffffffffff","USDC":null},"Polkadot":{"DOT":"0x7ffffffffffffffe"}},"minimum_deposit_amounts":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffff","USDC":"0x7ffffffffffffffe"},"Polkadot":{"DOT":0}},"witness_safety_margins":{"Bitcoin":3,"Ethereum":3,"Polkadot":null}},"pools":{"fees":{"Ethereum":{"FLIP":{"limit_order_fee_hundredth_pips":0,"quote_asset":{"asset":"USDC","chain":"Ethereum"},"range_order_fee_hundredth_pips":100}}}},"swapping":{"maximum_swap_amounts":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":null,"USDC":"0x7ffffffffffffffe"},"Polkadot":{"DOT":null}},"network_fee_hundredth_pips":1000000}} diff --git a/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs b/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs index b2d41b74e1..c4868d2073 100644 --- a/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs +++ b/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs @@ -69,23 +69,6 @@ mod benchmarks { )); } } - - #[benchmark] - fn set_minimum_deposit() { - let origin = T::EnsureGovernance::try_successful_origin().unwrap(); - let destination_asset: <>::TargetChain as Chain>::ChainAsset = - BenchmarkValue::benchmark_value(); - let amount: <>::TargetChain as Chain>::ChainAmount = - BenchmarkValue::benchmark_value(); - - #[block] - { - assert_ok!(Pallet::::set_minimum_deposit(origin, destination_asset, amount)); - } - - assert_eq!(MinimumDeposit::::get(destination_asset,), amount); - } - #[benchmark] fn finalise_ingress(a: Linear<1, 100>) { let mut addresses = vec![]; @@ -177,9 +160,6 @@ mod benchmarks { new_test_ext().execute_with(|| { _finalise_ingress::(100, true); }); - new_test_ext().execute_with(|| { - _set_minimum_deposit::(true); - }); new_test_ext().execute_with(|| { _process_single_deposit::(true); }); diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index f93141e2ad..c4cc9257dd 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -31,7 +31,7 @@ use cf_primitives::{ use cf_traits::{ liquidity::{LpBalanceApi, LpDepositHandler}, AssetConverter, Broadcaster, CcmHandler, CcmSwapIds, Chainflip, DepositApi, DepositHandler, - EgressApi, EpochInfo, GetBlockHeight, GetTrackedData, NetworkEnvironmentProvider, + EgressApi, EpochInfo, FeePayment, GetBlockHeight, GetTrackedData, NetworkEnvironmentProvider, ScheduledEgressDetails, SwapDepositHandler, }; use frame_support::{ @@ -114,6 +114,24 @@ pub struct FailedForeignChainCall { pub original_epoch: EpochIndex, } +#[derive( + CloneNoBound, + RuntimeDebugNoBound, + PartialEqNoBound, + EqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T, I))] +pub enum PalletConfigUpdate, I: 'static = ()> { + /// Set the fixed fee that is burned when opening a channel, denominated in Flipperinos. + ChannelOpeningFee { fee: T::Amount }, + /// Set the minimum deposit allowed for a particular asset. + SetMinimumDeposit { asset: TargetChainAsset, minimum_deposit: TargetChainAmount }, +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -126,6 +144,7 @@ pub mod pallet { traits::{ConstU128, EnsureOrigin, IsType}, DefaultNoBound, }; + use frame_system::WeightInfo as SystemWeightInfo; use sp_std::vec::Vec; pub(crate) type ChannelRecycleQueue = @@ -339,6 +358,9 @@ pub mod pallet { /// Allows assets to be converted through the AMM. type AssetConverter: AssetConverter; + /// For paying the channel opening fee. + type FeePayment: FeePayment; + /// Benchmark weights type WeightInfo: WeightInfo; } @@ -432,6 +454,12 @@ pub mod pallet { pub type WithheldTransactionFees, I: 'static = ()> = StorageMap<_, Twox64Concat, TargetChainAsset, TargetChainAmount, ValueQuery>; + /// The fixed fee charged for opening a channel, in Flipperinos. + #[pallet::storage] + #[pallet::getter(fn channel_opening_fee)] + pub type ChannelOpeningFee, I: 'static = ()> = + StorageValue<_, T::Amount, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -505,6 +533,12 @@ pub mod pallet { UtxoConsolidation { broadcast_id: BroadcastId, }, + ChannelOpeningFeePaid { + fee: T::Amount, + }, + ChannelOpeningFeeSet { + fee: T::Amount, + }, } #[derive(CloneNoBound, PartialEqNoBound, EqNoBound)] @@ -726,27 +760,6 @@ pub mod pallet { Ok(()) } - /// Sets the minimum deposit amount allowed for an asset. - /// Requires governance - /// - /// ## Events - /// - /// - [on_success](Event::MinimumDepositSet) - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::set_minimum_deposit())] - pub fn set_minimum_deposit( - origin: OriginFor, - asset: TargetChainAsset, - minimum_deposit: TargetChainAmount, - ) -> DispatchResult { - T::EnsureGovernance::ensure_origin(origin)?; - - MinimumDeposit::::insert(asset, minimum_deposit); - - Self::deposit_event(Event::::MinimumDepositSet { asset, minimum_deposit }); - Ok(()) - } - /// Stores information on failed Vault transfer. /// Requires Witness origin. /// @@ -819,6 +832,37 @@ pub mod pallet { Self::deposit_event(Event::::CcmBroadcastFailed { broadcast_id }); Ok(()) } + + /// Apply a list of configuration updates to the pallet. + /// + /// Requires Governance. + #[pallet::call_index(6)] + #[pallet::weight(::SystemWeightInfo::set_storage(updates.len() as u32))] + pub fn update_pallet_config( + origin: OriginFor, + updates: BoundedVec, ConstU32<10>>, + ) -> DispatchResult { + T::EnsureGovernance::ensure_origin(origin)?; + + for update in updates { + match update { + PalletConfigUpdate::::ChannelOpeningFee { fee } => { + let fee = fee.unique_saturated_into(); + ChannelOpeningFee::::set(fee); + Self::deposit_event(Event::::ChannelOpeningFeeSet { fee }); + }, + PalletConfigUpdate::::SetMinimumDeposit { asset, minimum_deposit } => { + MinimumDeposit::::insert(asset, minimum_deposit); + Self::deposit_event(Event::::MinimumDepositSet { + asset, + minimum_deposit, + }); + }, + } + } + + Ok(()) + } } } @@ -1148,13 +1192,20 @@ impl, I: 'static> Pallet { /// Opens a channel for the given asset and registers it with the given action. /// /// May re-use an existing deposit address, depending on chain configuration. + /// + /// The requester must have enough FLIP available to pay the channel opening fee. #[allow(clippy::type_complexity)] fn open_channel( + requester: &T::AccountId, source_asset: TargetChainAsset, action: ChannelAction, boost_fee: BasisPoints, ) -> Result<(ChannelId, TargetChainAccount, TargetChainBlockNumber), DispatchError> { + let channel_opening_fee = ChannelOpeningFee::::get(); + T::FeePayment::try_burn_fee(requester, channel_opening_fee)?; + Self::deposit_event(Event::::ChannelOpeningFeePaid { fee: channel_opening_fee }); + let (deposit_channel, channel_id) = if let Some((channel_id, mut deposit_channel)) = DepositChannelPool::::drain().next() { @@ -1348,8 +1399,9 @@ impl, I: 'static> DepositApi for Pallet { DispatchError, > { let (channel_id, deposit_address, expiry_block) = Self::open_channel( + &lp_account, source_asset, - ChannelAction::LiquidityProvision { lp_account }, + ChannelAction::LiquidityProvision { lp_account: lp_account.clone() }, boost_fee, )?; @@ -1370,6 +1422,7 @@ impl, I: 'static> DepositApi for Pallet { DispatchError, > { let (channel_id, deposit_address, expiry_height) = Self::open_channel( + &broker_id, source_asset, match channel_metadata { Some(msg) => ChannelAction::CcmTransfer { @@ -1381,7 +1434,7 @@ impl, I: 'static> DepositApi for Pallet { destination_asset, destination_address, broker_commission_bps, - broker_id, + broker_id: broker_id.clone(), }, }, boost_fee, diff --git a/state-chain/pallets/cf-ingress-egress/src/mock_btc.rs b/state-chain/pallets/cf-ingress-egress/src/mock_btc.rs index cbc2b43847..96b202126d 100644 --- a/state-chain/pallets/cf-ingress-egress/src/mock_btc.rs +++ b/state-chain/pallets/cf-ingress-egress/src/mock_btc.rs @@ -20,6 +20,7 @@ use cf_traits::{ asset_converter::MockAssetConverter, broadcaster::MockBroadcaster, ccm_handler::MockCcmHandler, + fee_payment::MockFeePayment, lp_balance::MockBalance, }, DepositHandler, NetworkEnvironmentProvider, SwapDepositHandler, @@ -137,6 +138,7 @@ impl pallet_cf_ingress_egress::Config for Test { type WeightInfo = (); type NetworkEnvironment = MockNetworkEnvironmentProvider; type AssetConverter = MockAssetConverter; + type FeePayment = MockFeePayment; } impl_test_helpers! { diff --git a/state-chain/pallets/cf-ingress-egress/src/mock_eth.rs b/state-chain/pallets/cf-ingress-egress/src/mock_eth.rs index dea73df345..7b504454a7 100644 --- a/state-chain/pallets/cf-ingress-egress/src/mock_eth.rs +++ b/state-chain/pallets/cf-ingress-egress/src/mock_eth.rs @@ -21,6 +21,7 @@ use cf_traits::{ asset_converter::MockAssetConverter, broadcaster::MockBroadcaster, ccm_handler::MockCcmHandler, + fee_payment::MockFeePayment, lp_balance::MockBalance, swap_deposit_handler::MockSwapDepositHandler, }, @@ -122,6 +123,7 @@ impl crate::Config for Test { type WeightInfo = (); type NetworkEnvironment = MockNetworkEnvironmentProvider; type AssetConverter = MockAssetConverter; + type FeePayment = MockFeePayment; } pub const ALICE: ::AccountId = 123u64; diff --git a/state-chain/pallets/cf-ingress-egress/src/tests.rs b/state-chain/pallets/cf-ingress-egress/src/tests.rs index e6d2e9c770..6a4e8bbbf7 100644 --- a/state-chain/pallets/cf-ingress-egress/src/tests.rs +++ b/state-chain/pallets/cf-ingress-egress/src/tests.rs @@ -1,9 +1,10 @@ use crate::{ - mock_eth::*, Call as PalletCall, ChannelAction, ChannelIdCounter, CrossChainMessage, - DepositAction, DepositChannelLookup, DepositChannelPool, DepositIgnoredReason, DepositWitness, - DisabledEgressAssets, EgressDustLimit, Event as PalletEvent, FailedForeignChainCall, - FailedForeignChainCalls, FetchOrTransfer, MinimumDeposit, Pallet, ScheduledEgressCcm, - ScheduledEgressFetchOrTransfer, TargetChainAccount, + mock_eth::*, Call as PalletCall, ChannelAction, ChannelIdCounter, ChannelOpeningFee, + CrossChainMessage, DepositAction, DepositChannelLookup, DepositChannelPool, + DepositIgnoredReason, DepositWitness, DisabledEgressAssets, EgressDustLimit, + Event as PalletEvent, FailedForeignChainCall, FailedForeignChainCalls, FetchOrTransfer, + MinimumDeposit, Pallet, PalletConfigUpdate, ScheduledEgressCcm, ScheduledEgressFetchOrTransfer, + TargetChainAccount, }; use cf_chains::{ address::AddressConverter, evm::EvmFetchId, mocks::MockEthereum, CcmChannelMetadata, @@ -13,13 +14,15 @@ use cf_primitives::{chains::assets::eth, ChannelId, ForeignChain}; use cf_test_utilities::assert_has_event; use cf_traits::{ mocks::{ + self, address_converter::MockAddressConverter, api_call::{MockEthAllBatch, MockEthEnvironment, MockEthereumApiCall}, block_height_provider::BlockHeightProvider, ccm_handler::{CcmRequest, MockCcmHandler}, + funding_info::MockFundingInfo, tracked_data_provider::TrackedDataProvider, }, - DepositApi, EgressApi, EpochInfo, GetBlockHeight, ScheduledEgressDetails, + DepositApi, EgressApi, EpochInfo, FundingInfo, GetBlockHeight, ScheduledEgressDetails, }; use frame_support::{ assert_err, assert_ok, @@ -438,6 +441,7 @@ fn reused_address_channel_id_matches() { .unwrap(); DepositChannelPool::::insert(CHANNEL_ID, new_channel.clone()); let (reused_channel_id, reused_address, ..) = IngressEgress::open_channel( + &ALICE, eth::Asset::Eth, ChannelAction::LiquidityProvision { lp_account: 0 }, 0, @@ -839,10 +843,11 @@ fn can_set_minimum_deposit() { let minimum_deposit = 1_500u128; assert_eq!(MinimumDeposit::::get(asset), 0); // Set the new minimum deposits - assert_ok!(IngressEgress::set_minimum_deposit( + assert_ok!(IngressEgress::update_pallet_config( RuntimeOrigin::root(), - asset, - minimum_deposit + vec![PalletConfigUpdate::::SetMinimumDeposit { asset, minimum_deposit }] + .try_into() + .unwrap() )); assert_eq!(MinimumDeposit::::get(asset), minimum_deposit); @@ -861,11 +866,20 @@ fn deposits_below_minimum_are_rejected() { let default_deposit_amount = 1_000; // Set minimum deposit - assert_ok!(IngressEgress::set_minimum_deposit(RuntimeOrigin::root(), eth, 1_500)); - assert_ok!(IngressEgress::set_minimum_deposit( + assert_ok!(IngressEgress::update_pallet_config( RuntimeOrigin::root(), - flip, - default_deposit_amount + vec![ + PalletConfigUpdate::::SetMinimumDeposit { + asset: eth, + minimum_deposit: 1_500 + }, + PalletConfigUpdate::::SetMinimumDeposit { + asset: flip, + minimum_deposit: default_deposit_amount + }, + ] + .try_into() + .unwrap() )); // Observe that eth deposit gets rejected. @@ -1474,3 +1488,67 @@ fn consolidation_tx_gets_broadcasted_on_finalize() { )); }); } + +#[test] +fn broker_pays_a_fee_for_each_deposit_address() { + new_test_ext().execute_with(|| { + const CHANNEL_REQUESTER: u64 = 789; + const FEE: u128 = 100; + MockFundingInfo::::credit_funds(&CHANNEL_REQUESTER, FEE); + assert_eq!(MockFundingInfo::::total_balance_of(&CHANNEL_REQUESTER), FEE); + assert_ok!(IngressEgress::update_pallet_config( + OriginTrait::root(), + vec![PalletConfigUpdate::ChannelOpeningFee { fee: FEE }].try_into().unwrap() + )); + assert_ok!(IngressEgress::open_channel( + &CHANNEL_REQUESTER, + eth::Asset::Eth, + ChannelAction::LiquidityProvision { lp_account: CHANNEL_REQUESTER }, + 0 + )); + assert_eq!(MockFundingInfo::::total_balance_of(&CHANNEL_REQUESTER), 0); + assert_ok!(IngressEgress::update_pallet_config( + OriginTrait::root(), + vec![PalletConfigUpdate::ChannelOpeningFee { fee: FEE * 10 }] + .try_into() + .unwrap() + )); + assert_err!( + IngressEgress::open_channel( + &CHANNEL_REQUESTER, + eth::Asset::Eth, + ChannelAction::LiquidityProvision { lp_account: CHANNEL_REQUESTER }, + 0 + ), + mocks::fee_payment::ERROR_INSUFFICIENT_LIQUIDITY + ); + }); +} + +#[test] +fn can_update_multiple_items_at_once() { + new_test_ext().execute_with(|| { + assert_eq!(ChannelOpeningFee::::get(), 0); + assert_eq!(MinimumDeposit::::get(eth::Asset::Flip), 0); + assert_eq!(MinimumDeposit::::get(eth::Asset::Eth), 0); + assert_ok!(IngressEgress::update_pallet_config( + OriginTrait::root(), + vec![ + PalletConfigUpdate::ChannelOpeningFee { fee: 100 }, + PalletConfigUpdate::SetMinimumDeposit { + asset: eth::Asset::Flip, + minimum_deposit: 100 + }, + PalletConfigUpdate::SetMinimumDeposit { + asset: eth::Asset::Eth, + minimum_deposit: 200 + }, + ] + .try_into() + .unwrap() + )); + assert_eq!(ChannelOpeningFee::::get(), 100); + assert_eq!(MinimumDeposit::::get(eth::Asset::Flip), 100); + assert_eq!(MinimumDeposit::::get(eth::Asset::Eth), 200); + }); +} diff --git a/state-chain/pallets/cf-ingress-egress/src/weights.rs b/state-chain/pallets/cf-ingress-egress/src/weights.rs index c40635dbbf..c2a0c85919 100644 --- a/state-chain/pallets/cf-ingress-egress/src/weights.rs +++ b/state-chain/pallets/cf-ingress-egress/src/weights.rs @@ -34,7 +34,6 @@ use core::marker::PhantomData; pub trait WeightInfo { fn disable_asset_egress() -> Weight; fn process_single_deposit() -> Weight; - fn set_minimum_deposit() -> Weight; fn finalise_ingress(a: u32, ) -> Weight; fn vault_transfer_failed() -> Weight; fn ccm_broadcast_failed() -> Weight; @@ -75,16 +74,6 @@ impl WeightInfo for PalletWeight { .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: `EthereumIngressEgress::MinimumDeposit` (r:0 w:1) - /// Proof: `EthereumIngressEgress::MinimumDeposit` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn set_minimum_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 10_089_000 picoseconds. - Weight::from_parts(10_496_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } /// Storage: `EthereumIngressEgress::DepositChannelLookup` (r:1 w:1) /// Proof: `EthereumIngressEgress::DepositChannelLookup` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `a` is `[1, 100]`. @@ -150,16 +139,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: `EthereumIngressEgress::MinimumDeposit` (r:0 w:1) - /// Proof: `EthereumIngressEgress::MinimumDeposit` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn set_minimum_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 10_089_000 picoseconds. - Weight::from_parts(10_496_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } /// Storage: `EthereumIngressEgress::DepositChannelLookup` (r:1 w:1) /// Proof: `EthereumIngressEgress::DepositChannelLookup` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `a` is `[1, 100]`. diff --git a/state-chain/pallets/cf-swapping/src/benchmarking.rs b/state-chain/pallets/cf-swapping/src/benchmarking.rs index 2433f6f954..91157129a2 100644 --- a/state-chain/pallets/cf-swapping/src/benchmarking.rs +++ b/state-chain/pallets/cf-swapping/src/benchmarking.rs @@ -3,7 +3,7 @@ use super::*; use cf_chains::{address::EncodedAddress, benchmarking_value::BenchmarkValue}; -use cf_traits::{AccountRoleRegistry, Chainflip}; +use cf_traits::AccountRoleRegistry; use frame_benchmarking::v2::*; use frame_support::{ assert_ok, @@ -129,21 +129,5 @@ mod benchmarks { ); } - #[benchmark] - fn set_maximum_swap_amount() { - let asset = Asset::Eth; - let amount = 1_000; - let call = Call::::set_maximum_swap_amount { asset, amount: Some(amount) }; - - #[block] - { - assert_ok!(call.dispatch_bypass_filter( - ::EnsureGovernance::try_successful_origin().unwrap() - )); - } - - assert_eq!(crate::MaximumSwapAmount::::get(asset), Some(amount)); - } - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); } diff --git a/state-chain/pallets/cf-swapping/src/lib.rs b/state-chain/pallets/cf-swapping/src/lib.rs index 06db232720..6585e680b8 100644 --- a/state-chain/pallets/cf-swapping/src/lib.rs +++ b/state-chain/pallets/cf-swapping/src/lib.rs @@ -176,6 +176,12 @@ pub enum CcmFailReason { InsufficientDepositAmount, } +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum PalletConfigUpdate { + /// Set the maximum amount allowed to be put into a swap. Excess amounts are confiscated. + MaximumSwapAmount { asset: Asset, amount: Option }, +} + impl_pallet_safe_mode! { PalletSafeMode; swaps_enabled, withdrawals_enabled, deposits_enabled, broker_registration_enabled, } @@ -189,6 +195,7 @@ pub mod pallet { AccountRoleRegistry, CcmSwapIds, Chainflip, EgressApi, ScheduledEgressDetails, SwapDepositHandler, }; + use frame_system::WeightInfo as SystemWeightInfo; use super::*; @@ -620,28 +627,26 @@ pub mod pallet { Ok(()) } - /// Sets the Maximum amount allowed in a single swap for an asset. + /// Apply a list of configuration updates to the pallet. /// /// Requires Governance. - /// - /// ## Events - /// - /// - [On update](Event::MaximumSwapAmountSet) - #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::set_maximum_swap_amount())] - pub fn set_maximum_swap_amount( + #[pallet::call_index(8)] + #[pallet::weight(::SystemWeightInfo::set_storage(updates.len() as u32))] + pub fn update_pallet_config( origin: OriginFor, - asset: Asset, - amount: Option, + updates: BoundedVec>, ) -> DispatchResult { T::EnsureGovernance::ensure_origin(origin)?; - match amount { - Some(max) => MaximumSwapAmount::::insert(asset, max), - None => MaximumSwapAmount::::remove(asset), - }; + for update in updates { + match update { + PalletConfigUpdate::MaximumSwapAmount { asset, amount } => { + MaximumSwapAmount::::set(asset, amount); + Self::deposit_event(Event::::MaximumSwapAmountSet { asset, amount }); + }, + } + } - Self::deposit_event(Event::::MaximumSwapAmountSet { asset, amount }); Ok(()) } } diff --git a/state-chain/pallets/cf-swapping/src/mock.rs b/state-chain/pallets/cf-swapping/src/mock.rs index 8752580b85..12e954fe30 100644 --- a/state-chain/pallets/cf-swapping/src/mock.rs +++ b/state-chain/pallets/cf-swapping/src/mock.rs @@ -129,10 +129,6 @@ impl WeightInfo for MockWeightInfo { fn register_as_broker() -> Weight { Weight::from_parts(100, 0) } - - fn set_maximum_swap_amount() -> Weight { - Weight::from_parts(100, 0) - } } impl pallet_cf_swapping::Config for Test { diff --git a/state-chain/pallets/cf-swapping/src/tests.rs b/state-chain/pallets/cf-swapping/src/tests.rs index 8d3bfc523e..4335e8c5f4 100644 --- a/state-chain/pallets/cf-swapping/src/tests.rs +++ b/state-chain/pallets/cf-swapping/src/tests.rs @@ -20,13 +20,25 @@ use cf_traits::{ }, CcmHandler, SetSafeMode, SwapDepositHandler, SwappingApi, }; -use frame_support::{assert_err, assert_noop, assert_ok, traits::Hooks}; +use frame_support::{ + assert_err, assert_noop, assert_ok, + traits::{Hooks, OriginTrait}, +}; use itertools::Itertools; use sp_arithmetic::Permill; use sp_std::iter; const GAS_BUDGET: AssetAmount = 1_000u128; +fn set_maximum_swap_amount(asset: Asset, amount: Option) { + assert_ok!(Swapping::update_pallet_config( + OriginTrait::root(), + vec![PalletConfigUpdate::MaximumSwapAmount { asset, amount }] + .try_into() + .unwrap() + )); +} + // Returns some test data fn generate_test_swaps() -> Vec { vec![ @@ -1442,7 +1454,7 @@ fn can_set_maximum_swap_amount() { assert!(MaximumSwapAmount::::get(asset).is_none()); // Set the new maximum swap_amount - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), asset, amount)); + set_maximum_swap_amount(asset, amount); assert_eq!(MaximumSwapAmount::::get(asset), amount); assert_eq!(Swapping::maximum_swap_amount(asset), amount); @@ -1453,7 +1465,7 @@ fn can_set_maximum_swap_amount() { })); // Can remove maximum swap amount - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), asset, None)); + set_maximum_swap_amount(asset, None); assert!(MaximumSwapAmount::::get(asset).is_none()); System::assert_last_event(RuntimeEvent::Swapping(Event::::MaximumSwapAmountSet { asset, @@ -1473,7 +1485,7 @@ fn swap_excess_are_confiscated_ccm_via_deposit() { let request_ccm = generate_ccm_channel(); let ccm = generate_ccm_deposit(); - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, Some(max_swap))); + set_maximum_swap_amount(from, Some(max_swap)); // Register CCM via Swap deposit assert_ok!(Swapping::request_swap_deposit_address( @@ -1552,7 +1564,7 @@ fn swap_excess_are_confiscated_ccm_via_extrinsic() { let to: Asset = Asset::Flip; let ccm = generate_ccm_deposit(); - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, Some(max_swap))); + set_maximum_swap_amount(from, Some(max_swap)); // Register CCM via Swap deposit assert_ok!(Swapping::ccm_deposit( @@ -1620,7 +1632,7 @@ fn swap_excess_are_confiscated_for_swap_via_extrinsic() { let from: Asset = Asset::Usdc; let to: Asset = Asset::Flip; - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, Some(max_swap))); + set_maximum_swap_amount(from, Some(max_swap)); assert_ok!(Swapping::schedule_swap_from_contract( RuntimeOrigin::signed(ALICE), @@ -1665,7 +1677,7 @@ fn swap_excess_are_confiscated_for_swap_via_deposit() { let from: Asset = Asset::Usdc; let to: Asset = Asset::Flip; - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, Some(max_swap))); + set_maximum_swap_amount(from, Some(max_swap)); Swapping::schedule_swap_from_channel( ForeignChainAddress::Eth(Default::default()), @@ -1714,7 +1726,7 @@ fn max_swap_amount_can_be_removed() { let to: Asset = Asset::Flip; // Initial max swap amount is set. - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, Some(max_swap))); + set_maximum_swap_amount(from, Some(max_swap)); assert_ok!(Swapping::schedule_swap_from_contract( RuntimeOrigin::signed(ALICE), from, @@ -1731,7 +1743,7 @@ fn max_swap_amount_can_be_removed() { System::reset_events(); // Max is removed. - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, None)); + set_maximum_swap_amount(from, None); assert_ok!(Swapping::schedule_swap_from_contract( RuntimeOrigin::signed(ALICE), @@ -1784,7 +1796,7 @@ fn can_swap_below_max_amount() { let to: Asset = Asset::Flip; // Initial max swap amount is set. - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, Some(max_swap))); + set_maximum_swap_amount(from, Some(max_swap)); assert_ok!(Swapping::schedule_swap_from_contract( RuntimeOrigin::signed(ALICE), from, @@ -1822,7 +1834,7 @@ fn can_swap_ccm_below_max_amount() { let to: Asset = Asset::Flip; let ccm = generate_ccm_deposit(); - assert_ok!(Swapping::set_maximum_swap_amount(RuntimeOrigin::root(), from, Some(max_swap))); + set_maximum_swap_amount(from, Some(max_swap)); // Register CCM via Swap deposit assert_ok!(Swapping::ccm_deposit( @@ -2128,3 +2140,22 @@ fn deposit_address_ready_event_contain_correct_boost_fee_value() { ); }); } + +#[test] +fn can_update_multiple_items_at_once() { + new_test_ext().execute_with(|| { + assert!(MaximumSwapAmount::::get(Asset::Btc).is_none()); + assert!(MaximumSwapAmount::::get(Asset::Dot).is_none()); + assert_ok!(Swapping::update_pallet_config( + OriginTrait::root(), + vec![ + PalletConfigUpdate::MaximumSwapAmount { asset: Asset::Btc, amount: Some(100) }, + PalletConfigUpdate::MaximumSwapAmount { asset: Asset::Dot, amount: Some(200) }, + ] + .try_into() + .unwrap() + )); + assert_eq!(MaximumSwapAmount::::get(Asset::Btc), Some(100)); + assert_eq!(MaximumSwapAmount::::get(Asset::Dot), Some(200)); + }); +} diff --git a/state-chain/pallets/cf-swapping/src/weights.rs b/state-chain/pallets/cf-swapping/src/weights.rs index a19ca60c60..715a10909a 100644 --- a/state-chain/pallets/cf-swapping/src/weights.rs +++ b/state-chain/pallets/cf-swapping/src/weights.rs @@ -37,7 +37,6 @@ pub trait WeightInfo { fn register_as_broker() -> Weight; fn schedule_swap_from_contract() -> Weight; fn ccm_deposit() -> Weight; - fn set_maximum_swap_amount() -> Weight; } /// Weights for pallet_cf_swapping using the Substrate node and recommended hardware. @@ -140,17 +139,6 @@ impl WeightInfo for PalletWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } - - /// Storage: `Swapping::MinimumSwapAmount` (r:0 w:1) - /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn set_maximum_swap_amount() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 10_400_000 picoseconds. - Weight::from_parts(10_718_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } } // For backwards compatibility and tests @@ -252,15 +240,4 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } - - /// Storage: `Swapping::MinimumSwapAmount` (r:0 w:1) - /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn set_maximum_swap_amount() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 10_400_000 picoseconds. - Weight::from_parts(10_718_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } } diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index bbfd98eb34..06cd45a410 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -287,6 +287,7 @@ impl pallet_cf_ingress_egress::Config for Runtime { type WeightInfo = pallet_cf_ingress_egress::weights::PalletWeight; type NetworkEnvironment = Environment; type AssetConverter = LiquidityPools; + type FeePayment = Flip; } impl pallet_cf_ingress_egress::Config for Runtime { @@ -305,6 +306,7 @@ impl pallet_cf_ingress_egress::Config for Runtime { type CcmHandler = Swapping; type NetworkEnvironment = Environment; type AssetConverter = LiquidityPools; + type FeePayment = Flip; } impl pallet_cf_ingress_egress::Config for Runtime { @@ -323,6 +325,7 @@ impl pallet_cf_ingress_egress::Config for Runtime { type CcmHandler = Swapping; type NetworkEnvironment = Environment; type AssetConverter = LiquidityPools; + type FeePayment = Flip; } parameter_types! { @@ -1454,6 +1457,14 @@ impl_runtime_apis! { Some(result) } + + fn cf_channel_opening_fee(chain: ForeignChain) -> FlipBalance { + match chain { + ForeignChain::Ethereum => pallet_cf_ingress_egress::Pallet::::channel_opening_fee(), + ForeignChain::Polkadot => pallet_cf_ingress_egress::Pallet::::channel_opening_fee(), + ForeignChain::Bitcoin => pallet_cf_ingress_egress::Pallet::::channel_opening_fee(), + } + } } // END custom runtime APIs diff --git a/state-chain/runtime/src/runtime_apis.rs b/state-chain/runtime/src/runtime_apis.rs index cedcd49797..80ead5d508 100644 --- a/state-chain/runtime/src/runtime_apis.rs +++ b/state-chain/runtime/src/runtime_apis.rs @@ -5,8 +5,8 @@ use cf_amm::{ }; use cf_chains::{eth::Address as EthereumAddress, Chain, ForeignChainAddress}; use cf_primitives::{ - AccountRole, Asset, AssetAmount, BroadcastId, EpochIndex, ForeignChain, NetworkEnvironment, - SemVer, SwapOutput, + AccountRole, Asset, AssetAmount, BroadcastId, EpochIndex, FlipBalance, ForeignChain, + NetworkEnvironment, SemVer, SwapOutput, }; use codec::{Decode, Encode}; use core::ops::Range; @@ -192,5 +192,6 @@ decl_runtime_apis!( fn cf_egress_fee(asset: Asset) -> Option; fn cf_witness_count(hash: CallHash) -> Option; fn cf_witness_safety_margin(chain: ForeignChain) -> Option; + fn cf_channel_opening_fee(chain: ForeignChain) -> FlipBalance; } ); diff --git a/state-chain/traits/src/mocks/funding_info.rs b/state-chain/traits/src/mocks/funding_info.rs index af37995153..a9ef780d40 100644 --- a/state-chain/traits/src/mocks/funding_info.rs +++ b/state-chain/traits/src/mocks/funding_info.rs @@ -1,6 +1,9 @@ use crate::{Chainflip, FundingInfo}; use frame_support::Never; -use sp_runtime::{traits::CheckedSub, Saturating}; +use sp_runtime::{ + traits::{CheckedSub, Zero}, + Saturating, +}; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData}; use super::{MockPallet, MockPalletStorage}; @@ -30,6 +33,9 @@ impl MockFundingInfo { } pub fn try_debit_funds(account_id: &T::AccountId, amount: T::Amount) -> Option { + if amount.is_zero() { + return Some(amount) + } ::mutate_value( BALANCES, |storage: &mut Option>| {