Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for opening/closing private broker channels #5361

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions state-chain/pallets/cf-ingress-egress/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ use cf_traits::{
AssetWithholding, BalanceApi, BoostApi, Broadcaster, Chainflip, DepositApi, EgressApi,
EpochInfo, FeePayment, FetchesTransfersLimitProvider, GetBlockHeight, IngressEgressFeeApi,
IngressSink, IngressSource, NetworkEnvironmentProvider, OnDeposit, PoolApi,
ScheduledEgressDetails, SwapLimitsProvider, SwapRequestHandler, SwapRequestType,
PrivateChannelManager, ScheduledEgressDetails, SwapLimitsProvider, SwapRequestHandler,
SwapRequestType,
};
use frame_support::{
pallet_prelude::{OptionQuery, *},
Expand Down Expand Up @@ -589,6 +590,10 @@ pub mod pallet {
pub(crate) type FailedRejections<T: Config<I>, I: 'static = ()> =
StorageValue<_, Vec<TaintedTransactionDetails<T, I>>, ValueQuery>;

#[pallet::storage]
pub(crate) type BrokerPrivateChannels<T: Config<I>, I: 'static = ()> =
StorageMap<_, Identity, T::AccountId, ChannelId, OptionQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
Expand Down Expand Up @@ -785,6 +790,10 @@ pub mod pallet {
UnsupportedChain,
/// Transaction cannot be reported after being pre-witnessed or boosted.
TransactionAlreadyPrewitnessed,
/// Cannot open a private channel for a broker because one already exists.
PrivateChannelExistsForBroker,
/// Cannot close a private channel for a broker because it does not exist.
NoPrivateChannelExistsForBroker,
}

#[pallet::hooks]
Expand Down Expand Up @@ -2277,11 +2286,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
deposit_channel.asset = source_asset;
(deposit_channel, channel_id)
} else {
let next_channel_id =
ChannelIdCounter::<T, I>::try_mutate::<_, Error<T, I>, _>(|id| {
*id = id.checked_add(1).ok_or(Error::<T, I>::ChannelIdsExhausted)?;
Ok(*id)
})?;
let next_channel_id = Self::allocate_next_channel_id()?;
(
DepositChannel::generate_new::<T::AddressDerivation>(next_channel_id, source_asset)
.map_err(|e| match e {
Expand Down Expand Up @@ -2410,6 +2415,13 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Err(e) => log::error!("Ccm fallback failed to schedule the fallback egress: Target chain: {:?}, broadcast_id: {:?}, error: {:?}", T::TargetChain::get(), broadcast_id, e),
}
}

fn allocate_next_channel_id() -> Result<ChannelId, Error<T, I>> {
ChannelIdCounter::<T, I>::try_mutate::<_, Error<T, I>, _>(|id| {
*id = id.checked_add(1).ok_or(Error::<T, I>::ChannelIdsExhausted)?;
Ok(*id)
})
}
}

impl<T: Config<I>, I: 'static> EgressApi<T::TargetChain> for Pallet<T, I> {
Expand Down Expand Up @@ -2491,6 +2503,36 @@ impl<T: Config<I>, I: 'static> EgressApi<T::TargetChain> for Pallet<T, I> {
}
}

impl<T: Config<I>, I: 'static> PrivateChannelManager for Pallet<T, I> {
type AccountId = T::AccountId;

fn open_private_channel(broker_id: &Self::AccountId) -> Result<ChannelId, DispatchError> {
ensure!(
!BrokerPrivateChannels::<T, I>::contains_key(broker_id),
Error::<T, I>::PrivateChannelExistsForBroker
);

// TODO: burn fee for opening a channel?
let next_channel_id = Self::allocate_next_channel_id()?;

BrokerPrivateChannels::<T, I>::insert(broker_id.clone(), next_channel_id);

Ok(next_channel_id)
}

fn close_private_channel(broker_id: &Self::AccountId) -> Result<ChannelId, DispatchError> {
let Some(channel_id) = BrokerPrivateChannels::<T, I>::take(broker_id) else {
return Err(Error::<T, I>::NoPrivateChannelExistsForBroker.into())
};

Ok(channel_id)
}

fn private_channel_lookup(broker_id: &Self::AccountId) -> Option<ChannelId> {
BrokerPrivateChannels::<T, I>::get(broker_id)
}
}

impl<T: Config<I>, I: 'static> DepositApi<T::TargetChain> for Pallet<T, I> {
type AccountId = T::AccountId;
type Amount = T::Amount;
Expand Down
3 changes: 3 additions & 0 deletions state-chain/pallets/cf-ingress-egress/src/mock_eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ impl_test_helpers! {
priority_fee: Default::default()
}
);

<MockAccountRoleRegistry as cf_traits::AccountRoleRegistry<Test>>::register_as_broker(&BROKER).unwrap();

}
}

Expand Down
85 changes: 85 additions & 0 deletions state-chain/pallets/cf-ingress-egress/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1979,3 +1979,88 @@ fn failed_ccm_deposit_can_deposit_event() {
);
});
}

mod private_broker_channels {

use super::*;

use cf_traits::PrivateChannelManager;

#[test]
fn opening_new_channel() {
new_test_ext().execute_with(|| {
const REGULAR_CHANNEL_ID_1: u64 = 1;
const PRIVATE_CHANNEL_ID: u64 = 2;
const REGULAR_CHANNEL_ID_2: u64 = 3;

let open_regular_channel_expecting_id = |expected_channel_id: u64| {
let (channel_id, ..) = IngressEgress::open_channel(
&ALICE,
EthAsset::Eth,
ChannelAction::LiquidityProvision { lp_account: 0, refund_address: None },
0,
)
.unwrap();

assert_eq!(channel_id, expected_channel_id);
};

// Open a regular channel first to check that ids of regular
// and private channels do not overlap:
open_regular_channel_expecting_id(REGULAR_CHANNEL_ID_1);

assert_eq!(crate::BrokerPrivateChannels::<Test, ()>::get(BROKER), None);

// Opening a private channel should succeed:
{
assert_eq!(IngressEgress::open_private_channel(&BROKER), Ok(PRIVATE_CHANNEL_ID));

assert_eq!(
crate::BrokerPrivateChannels::<Test, ()>::get(BROKER),
Some(PRIVATE_CHANNEL_ID)
);
assert_eq!(
IngressEgress::private_channel_lookup(&BROKER),
Some(PRIVATE_CHANNEL_ID)
);
}

// Open a regular channel again to check that opening a private channel
// updates the channel id counter:
open_regular_channel_expecting_id(REGULAR_CHANNEL_ID_2);

// The same broker should not be able to open another private channel:
{
assert_noop!(
IngressEgress::open_private_channel(&BROKER),
crate::Error::<Test, ()>::PrivateChannelExistsForBroker
);
}
});
}

#[test]
fn closing_channel() {
new_test_ext().execute_with(|| {
// Channel not opened yet:
assert_noop!(
IngressEgress::close_private_channel(&BROKER),
crate::Error::<Test, ()>::NoPrivateChannelExistsForBroker
);

assert_eq!(IngressEgress::private_channel_lookup(&BROKER), None);

let channel_id = IngressEgress::open_private_channel(&BROKER)
.expect("should be able to open a private channel");

assert!(crate::BrokerPrivateChannels::<Test, ()>::get(BROKER).is_some());
assert_eq!(IngressEgress::private_channel_lookup(&BROKER), Some(channel_id));

// Should succeed now that the channel has been opened:
assert_eq!(IngressEgress::close_private_channel(&BROKER), Ok(channel_id));

assert_eq!(crate::BrokerPrivateChannels::<Test, ()>::get(BROKER), None);
assert_eq!(IngressEgress::private_channel_lookup(&BROKER), None);
});
}
}
45 changes: 44 additions & 1 deletion state-chain/pallets/cf-swapping/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use cf_traits::{AccountRoleRegistry, Chainflip, FeePayment};
use frame_benchmarking::v2::*;
use frame_support::{
assert_ok,
traits::{OnNewAccount, UnfilteredDispatchable},
traits::{OnNewAccount, OriginTrait, UnfilteredDispatchable},
};
use frame_system::RawOrigin;

Expand Down Expand Up @@ -127,5 +127,48 @@ mod benchmarks {
.expect_err("Caller should no longer be registered as broker");
}

#[benchmark]
fn open_private_channel() {
let broker_id =
T::AccountRoleRegistry::whitelisted_caller_with_role(AccountRole::Broker).unwrap();

let caller = OriginFor::<T>::signed(broker_id.clone());

#[block]
{
assert_ok!(Pallet::<T>::open_private_channel(caller, ForeignChain::Bitcoin));
}

assert!(
T::PrivateChannelManager::private_channel_lookup(&broker_id).is_some(),
"Private channel must have been opened"
);
}

#[benchmark]
fn close_private_channel() {
let broker_id =
T::AccountRoleRegistry::whitelisted_caller_with_role(AccountRole::Broker).unwrap();

let caller = OriginFor::<T>::signed(broker_id.clone());

assert_ok!(Pallet::<T>::open_private_channel(caller.clone(), ForeignChain::Bitcoin));

assert!(
T::PrivateChannelManager::private_channel_lookup(&broker_id).is_some(),
"Private channel must have been opened"
);

#[block]
{
assert_ok!(Pallet::<T>::close_private_channel(caller, ForeignChain::Bitcoin));
}

assert!(
T::PrivateChannelManager::private_channel_lookup(&broker_id).is_none(),
"Private channel must have been closed"
);
}

impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,);
}
53 changes: 51 additions & 2 deletions state-chain/pallets/cf-swapping/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use cf_primitives::{
use cf_runtime_utilities::log_or_panic;
use cf_traits::{
impl_pallet_safe_mode, BalanceApi, DepositApi, ExecutionCondition, IngressEgressFeeApi,
SwapLimitsProvider, SwapRequestHandler, SwapRequestType, SwapRequestTypeEncoded, SwapType,
SwappingApi,
PrivateChannelManager, SwapLimitsProvider, SwapRequestHandler, SwapRequestType,
SwapRequestTypeEncoded, SwapType, SwappingApi,
};
use frame_support::{
pallet_prelude::*,
Expand Down Expand Up @@ -441,6 +441,10 @@ pub mod pallet {

/// The balance API for interacting with the asset-balance pallet.
type BalanceApi: BalanceApi<AccountId = <Self as frame_system::Config>::AccountId>;

type PrivateChannelManager: PrivateChannelManager<
AccountId = <Self as frame_system::Config>::AccountId,
>;
}

#[pallet::pallet]
Expand Down Expand Up @@ -642,6 +646,14 @@ pub mod pallet {
asset: Asset,
amount: AssetAmount,
},
PrivateBrokerChannelOpened {
broker_id: T::AccountId,
channel_id: ChannelId,
},
PrivateBrokerChannelClosed {
broker_id: T::AccountId,
channel_id: ChannelId,
},
}
#[pallet::error]
pub enum Error<T> {
Expand Down Expand Up @@ -683,6 +695,10 @@ pub mod pallet {
SwapRequestDurationTooLong,
/// Invalid DCA parameters.
InvalidDcaParameters,
/// Private channels are not supported for chain.
NoPrivateChannelsForChain,
/// Broker must close their private channels before deregistering
PrivateChannelNotClosed,
/// The provided Refund address cannot be decoded into ForeignChainAddress.
InvalidRefundAddress,
}
Expand Down Expand Up @@ -940,6 +956,11 @@ pub mod pallet {
pub fn deregister_as_broker(who: OriginFor<T>) -> DispatchResult {
let account_id = T::AccountRoleRegistry::ensure_broker(who)?;

ensure!(
T::PrivateChannelManager::private_channel_lookup(&account_id).is_none(),
Error::<T>::PrivateChannelNotClosed
);

ensure!(
T::BalanceApi::free_balances(&account_id).iter().all(|(_, amount)| *amount == 0),
Error::<T>::EarnedFeesNotWithdrawn,
Expand Down Expand Up @@ -1054,6 +1075,34 @@ pub mod pallet {

Ok(())
}

#[pallet::call_index(12)]
#[pallet::weight(T::WeightInfo::open_private_channel())]
pub fn open_private_channel(origin: OriginFor<T>, chain: ForeignChain) -> DispatchResult {
let broker_id = T::AccountRoleRegistry::ensure_broker(origin)?;

ensure!(chain == ForeignChain::Bitcoin, Error::<T>::NoPrivateChannelsForChain);

let channel_id = T::PrivateChannelManager::open_private_channel(&broker_id)?;

Self::deposit_event(Event::<T>::PrivateBrokerChannelOpened { broker_id, channel_id });

Ok(())
}

#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::close_private_channel())]
pub fn close_private_channel(origin: OriginFor<T>, chain: ForeignChain) -> DispatchResult {
let broker_id = T::AccountRoleRegistry::ensure_broker(origin)?;

ensure!(chain == ForeignChain::Bitcoin, Error::<T>::NoPrivateChannelsForChain);

let channel_id = T::PrivateChannelManager::close_private_channel(&broker_id)?;

Self::deposit_event(Event::<T>::PrivateBrokerChannelClosed { broker_id, channel_id });

Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down
Loading
Loading