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

feature/PRO-1038/pool-fee-rpc #4459

Merged
merged 19 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
16 changes: 16 additions & 0 deletions state-chain/amm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,22 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
self.range_orders.fee_hundredth_pips
}

pub fn range_order_total_fees_earned(&self) -> SideMap<Amount> {
self.range_orders.total_fees_earned
}

pub fn limit_order_total_fees_earned(&self) -> SideMap<Amount> {
self.limit_orders.total_fees_earned
}

pub fn range_order_swap_inputs(&self) -> SideMap<Amount> {
self.range_orders.total_swap_inputs
}

pub fn limit_order_swap_inputs(&self) -> SideMap<Amount> {
self.limit_orders.total_swap_inputs
}

pub fn limit_order_liquidity(&self, order: Order) -> Vec<(Tick, Amount)> {
match order {
Order::Sell => self.limit_orders.liquidity::<OneToZero>(),
Expand Down
4 changes: 2 additions & 2 deletions state-chain/amm/src/limit_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,9 @@ pub(super) struct PoolState<LiquidityProvider: Ord> {
/// Therefore there can be positions stored here that don't provide any liquidity.
positions: SideMap<BTreeMap<(SqrtPriceQ64F96, LiquidityProvider), Position>>,
/// Total fees earned over all time
total_fees_earned: SideMap<Amount>,
pub(super) total_fees_earned: SideMap<Amount>,
/// Total of all swap inputs over all time (not including fees)
total_swap_inputs: SideMap<Amount>,
pub(super) total_swap_inputs: SideMap<Amount>,
/// Total of all swap outputs over all time
total_swap_outputs: SideMap<Amount>,
}
Expand Down
4 changes: 2 additions & 2 deletions state-chain/amm/src/range_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ pub struct PoolState<LiquidityProvider: Ord> {
liquidity_map: BTreeMap<Tick, TickDelta>,
positions: BTreeMap<(LiquidityProvider, Tick, Tick), Position>,
/// Total fees earned over all time
total_fees_earned: SideMap<Amount>,
pub(super) total_fees_earned: SideMap<Amount>,
/// Total of all swap inputs over all time (not including fees)
total_swap_inputs: SideMap<Amount>,
pub(super) total_swap_inputs: SideMap<Amount>,
/// Total of all swap outputs over all time
total_swap_outputs: SideMap<Amount>,
}
Expand Down
7 changes: 7 additions & 0 deletions state-chain/cf-integration-tests/src/swapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use pallet_cf_broadcast::{
ThresholdSignatureData,
};
use pallet_cf_ingress_egress::{DepositWitness, FailedForeignChainCall};
use pallet_cf_lp::HistoricalEarnedFees;
use pallet_cf_pools::{OrderId, RangeOrderSize};
use pallet_cf_swapping::{CcmIdCounter, SWAP_DELAY_BLOCKS};
use sp_core::U256;
Expand Down Expand Up @@ -234,6 +235,7 @@ fn basic_pool_setup_provision_and_swap() {
credit_account(&DORIS, Asset::Eth, 1_000_000);
credit_account(&DORIS, Asset::Flip, 1_000_000);
credit_account(&DORIS, Asset::Usdc, 1_000_000);
assert!(!HistoricalEarnedFees::<Runtime>::contains_key(&DORIS, Asset::Usdc));

set_limit_order(&DORIS, Asset::Eth, Asset::Usdc, 0, Some(0), 500_000);
set_range_order(&DORIS, Asset::Eth, Asset::Usdc, 0, Some(-10..10), 1_000_000);
Expand Down Expand Up @@ -329,6 +331,11 @@ fn basic_pool_setup_provision_and_swap() {
) if egress_ids.contains(&egress_id) => ()
);

assert!(HistoricalEarnedFees::<Runtime>::contains_key(&DORIS, Asset::Usdc));
assert!(HistoricalEarnedFees::<Runtime>::contains_key(&DORIS, Asset::Flip));
assert!(HistoricalEarnedFees::<Runtime>::contains_key(&DORIS, Asset::Eth));
assert!(!HistoricalEarnedFees::<Runtime>::contains_key(&DORIS, Asset::Btc));

let usdc_balance_after = get_asset_balance(&DORIS, Asset::Usdc);
assert!(usdc_balance_after > usdc_balance_before, "Fees should be collected");
});
Expand Down
24 changes: 24 additions & 0 deletions state-chain/custom-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ pub enum RpcAccountInfo {
balances: HashMap<ForeignChain, HashMap<Asset, NumberOrHex>>,
refund_addresses: HashMap<ForeignChain, Option<ForeignChainAddressHumanreadable>>,
flip_balance: NumberOrHex,
earned_fees: BTreeMap<ForeignChain, Vec<RpcAssetWithAmount>>,
},
Validator {
flip_balance: NumberOrHex,
Expand Down Expand Up @@ -162,6 +163,21 @@ impl RpcAccountInfo {
.into_iter()
.map(|(chain, address)| (chain, address.map(|a| a.to_humanreadable(network))))
.collect(),
earned_fees: info
.earned_fees
.into_iter()
.map(|(chain, fees)| {
(
chain,
fees.into_iter()
.map(|(asset, amount)| RpcAssetWithAmount {
asset: asset.into(),
amount,
})
.collect(),
)
})
.collect(),
}
}

Expand Down Expand Up @@ -1429,6 +1445,10 @@ mod test {
(Asset::Btc, 0),
(Asset::Flip, u128::MAX / 2),
],
earned_fees: BTreeMap::from([(
ForeignChain::Bitcoin,
vec![(Asset::Btc, 1_000_000u32.into())],
)]),
},
cf_primitives::NetworkEnvironment::Mainnet,
0,
Expand Down Expand Up @@ -1556,6 +1576,10 @@ mod test {
PoolInfo {
limit_order_fee_hundredth_pips: 0,
range_order_fee_hundredth_pips: 100,
range_order_total_fees_earned: Default::default(),
limit_order_total_fees_earned: Default::default(),
range_total_swap_inputs: Default::default(),
limit_total_swap_inputs: Default::default(),
}
.into(),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: state-chain/custom-rpc/src/lib.rs
assertion_line: 1591
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"}},"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"}},"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"}},"network_fee_hundredth_pips":1000000}}
{"funding":{"minimum_funding_amount":0,"redemption_tax":0},"ingress_egress":{"egress_dust_limits":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":"0xffffffffffffffffffffffffffffffff","USDC":"0x7ffffffffffffffe"}},"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"}},"witness_safety_margins":{"Bitcoin":3,"Ethereum":3,"Polkadot":null}},"pools":{"fees":{"Ethereum":{"FLIP":{"limit_order_fee_hundredth_pips":0,"limit_order_total_fees_earned":{"one":"0x0","zero":"0x0"},"limit_total_swap_inputs":{"one":"0x0","zero":"0x0"},"quote_asset":{"asset":"USDC","chain":"Ethereum"},"range_order_fee_hundredth_pips":100,"range_order_total_fees_earned":{"one":"0x0","zero":"0x0"},"range_total_swap_inputs":{"one":"0x0","zero":"0x0"}}}}},"swapping":{"maximum_swap_amounts":{"Bitcoin":{"BTC":0},"Ethereum":{"ETH":0,"FLIP":null,"USDC":"0x7ffffffffffffffe"}},"network_fee_hundredth_pips":1000000}}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: state-chain/custom-rpc/src/lib.rs
assertion_line: 1457
expression: "serde_json::to_value(lp).unwrap()"
---
{"balances":{"Bitcoin":{"BTC":"0x0"},"Ethereum":{"ETH":"0xffffffffffffffffffffffffffffffff","FLIP":"0x7fffffffffffffffffffffffffffffff"}},"flip_balance":"0x0","refund_addresses":{"Bitcoin":null,"Ethereum":"0x0101010101010101010101010101010101010101","Polkadot":"111111111111111111111111111111111HC1"},"role":"liquidity_provider"}
{"balances":{"Bitcoin":{"BTC":"0x0"},"Ethereum":{"ETH":"0xffffffffffffffffffffffffffffffff","FLIP":"0x7fffffffffffffffffffffffffffffff"}},"earned_fees":{"Bitcoin":[{"amount":1000000,"asset":"BTC","chain":"Bitcoin"}]},"flip_balance":"0x0","refund_addresses":{"Bitcoin":null,"Ethereum":"0x0101010101010101010101010101010101010101","Polkadot":"111111111111111111111111111111111HC1"},"role":"liquidity_provider"}
9 changes: 9 additions & 0 deletions state-chain/pallets/cf-lp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ pub mod pallet {
pub type FreeBalances<T: Config> =
StorageDoubleMap<_, Twox64Concat, T::AccountId, Identity, Asset, AssetAmount>;

#[pallet::storage]
/// Historical earned fees for an account. Map: AccountId => AssetAmount
pub type HistoricalEarnedFees<T: Config> =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we could use the AssetMap here too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And use a StorageMap instead.

StorageDoubleMap<_, Twox64Concat, T::AccountId, Identity, Asset, AssetAmount, ValueQuery>;

/// Stores the registered energency withdrawal address for an Account
#[pallet::storage]
pub type LiquidityRefundAddress<T: Config> = StorageDoubleMap<
Expand Down Expand Up @@ -375,6 +380,10 @@ impl<T: Config> LpBalanceApi for Pallet<T> {
Ok(())
}

fn record_fees(account_id: &Self::AccountId, amount: AssetAmount, asset: Asset) {
HistoricalEarnedFees::<T>::mutate(account_id, asset, |fee| fee.saturating_add(amount));
}

fn asset_balances(who: &Self::AccountId) -> Vec<(Asset, AssetAmount)> {
let mut balances: Vec<(Asset, AssetAmount)> = vec![];
T::PoolApi::sweep(who).unwrap();
Expand Down
14 changes: 7 additions & 7 deletions state-chain/pallets/cf-pools/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,13 @@ mod benchmarks {
);
}

assert_eq!(
Pallet::<T>::pool_info(Asset::Eth, STABLE_ASSET),
Ok(PoolInfo {
limit_order_fee_hundredth_pips: fee,
range_order_fee_hundredth_pips: fee,
})
);
match Pallet::<T>::pool_info(Asset::Eth, STABLE_ASSET) {
Ok(pool_info) => {
assert_eq!(pool_info.limit_order_fee_hundredth_pips, fee);
assert_eq!(pool_info.range_order_fee_hundredth_pips, fee);
},
Err(_) => panic!("Pool not found"),
}
}

#[benchmark]
Expand Down
21 changes: 16 additions & 5 deletions state-chain/pallets/cf-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,14 @@ pub struct PoolInfo {
/// The fee taken, when range orders are used, from swap inputs that contributes to liquidity
/// provider earnings
pub range_order_fee_hundredth_pips: u32,
/// The total fees earned in this pool by range orders.
pub range_order_total_fees_earned: SideMap<Amount>,
Copy link
Contributor Author

@Janislav Janislav Feb 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the return value here. Maybe something scalar would make more sense? Same for all the over new added fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can use SideMap as you have here, and in the other PR of yours we will change it to PairMap (or whatever we call it).

/// The total fees earned in this pool by limit orders.
pub limit_order_total_fees_earned: SideMap<Amount>,
/// The total amount of assets that have been bought by range orders in this pool.
pub range_total_swap_inputs: SideMap<Amount>,
/// The total amount of assets that have been bought by limit orders in this pool.
pub limit_total_swap_inputs: SideMap<Amount>,
}

#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -1520,6 +1528,7 @@ impl<T: Config> Pallet<T> {
.try_map(|(asset, collected_fees)| {
AssetAmount::try_from(collected_fees).map_err(Into::into).and_then(
|collected_fees| {
T::LpBalance::record_fees(lp, collected_fees, asset);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlastairHolmes that's the place where I have to add record the fees for range orders, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry not sure how I could have missed that

T::LpBalance::try_credit_account(lp, asset, collected_fees)
.map(|()| collected_fees)
},
Expand Down Expand Up @@ -1804,6 +1813,10 @@ impl<T: Config> Pallet<T> {
Ok(PoolInfo {
limit_order_fee_hundredth_pips: pool.pool_state.limit_order_fee(),
range_order_fee_hundredth_pips: pool.pool_state.range_order_fee(),
range_order_total_fees_earned: pool.pool_state.range_order_total_fees_earned(),
limit_order_total_fees_earned: pool.pool_state.limit_order_total_fees_earned(),
range_total_swap_inputs: pool.pool_state.range_order_swap_inputs(),
limit_total_swap_inputs: pool.pool_state.limit_order_swap_inputs(),
})
}

Expand Down Expand Up @@ -1953,11 +1966,9 @@ impl<T: Config> Pallet<T> {
amount_change: IncreaseOrDecrease<AssetAmount>,
) -> DispatchResult {
let collected_fees: AssetAmount = collected.fees.try_into()?;
T::LpBalance::try_credit_account(
lp,
asset_pair.assets()[(!order.to_sold_side()).into()],
collected_fees,
)?;
let asset = asset_pair.assets()[(!order.to_sold_side()).into()];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add the recording of fees into inner_update_range_order too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I do this.

T::LpBalance::record_fees(lp, collected_fees, asset);
T::LpBalance::try_credit_account(lp, asset, collected_fees)?;

let bought_amount: AssetAmount = collected.bought_amount.try_into()?;
T::LpBalance::try_credit_account(
Expand Down
14 changes: 14 additions & 0 deletions state-chain/pallets/cf-pools/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
DispatchResult, Permill,
};
use sp_std::collections::btree_map::BTreeMap;

type AccountId = u64;

Expand Down Expand Up @@ -70,6 +71,7 @@ parameter_types! {
pub static AliceDebitedUsdc: AssetAmount = Default::default();
pub static BobDebitedEth: AssetAmount = Default::default();
pub static BobDebitedUsdc: AssetAmount = Default::default();
pub static RecordedFees: BTreeMap<AccountId, (Asset, AssetAmount)> = BTreeMap::new();
}
pub struct MockBalance;
impl LpBalanceApi for MockBalance {
Expand Down Expand Up @@ -120,11 +122,23 @@ impl LpBalanceApi for MockBalance {
Ok(())
}

fn record_fees(who: &Self::AccountId, amount: AssetAmount, asset: Asset) {
RecordedFees::mutate(|recorded_fees| {
recorded_fees.insert(*who, (asset, amount));
});
}

fn asset_balances(_who: &Self::AccountId) -> Vec<(Asset, AssetAmount)> {
unreachable!()
}
}

impl MockBalance {
pub fn assert_fees_recorded(who: &AccountId) {
assert!(RecordedFees::get().contains_key(who), "Fees not recorded for {:?}", who);
}
}

impl_mock_runtime_safe_mode!(pools: PalletSafeMode);
impl pallet_cf_pools::Config for Test {
type RuntimeEvent = RuntimeEvent;
Expand Down
57 changes: 56 additions & 1 deletion state-chain/pallets/cf-pools/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
CollectedNetworkFee, Error, Event, FlipBuyInterval, FlipToBurn, LimitOrder, PoolInfo,
PoolOrders, Pools, RangeOrder, RangeOrderSize, ScheduledLimitOrderUpdates, STABLE_ASSET,
};
use cf_amm::common::{price_at_tick, tick_at_price, Order, Tick, PRICE_FRACTIONAL_BITS};
use cf_amm::common::{price_at_tick, tick_at_price, Order, SideMap, Tick, PRICE_FRACTIONAL_BITS};
use cf_primitives::{chains::assets::any::Asset, AssetAmount, SwapOutput};
use cf_test_utilities::{assert_events_match, assert_has_event, last_event};
use cf_traits::AssetConverter;
Expand Down Expand Up @@ -289,6 +289,10 @@ fn can_update_pool_liquidity_fee_and_collect_for_limit_order() {
Ok(PoolInfo {
limit_order_fee_hundredth_pips: old_fee,
range_order_fee_hundredth_pips: old_fee,
range_order_total_fees_earned: Default::default(),
limit_order_total_fees_earned: Default::default(),
range_total_swap_inputs: Default::default(),
limit_total_swap_inputs: Default::default(),
})
);

Expand Down Expand Up @@ -409,8 +413,16 @@ fn can_update_pool_liquidity_fee_and_collect_for_limit_order() {
Ok(PoolInfo {
limit_order_fee_hundredth_pips: new_fee,
range_order_fee_hundredth_pips: new_fee,
range_order_total_fees_earned: Default::default(),
limit_order_total_fees_earned: SideMap {
zero: U256::from(4000),
one: U256::from(3992)
},
range_total_swap_inputs: Default::default(),
limit_total_swap_inputs: SideMap { zero: U256::from(6000), one: U256::from(5988) },
})
);

System::assert_has_event(RuntimeEvent::LiquidityPools(Event::<Test>::PoolFeeSet {
base_asset: Asset::Eth,
quote_asset: STABLE_ASSET,
Expand Down Expand Up @@ -639,6 +651,10 @@ fn update_pool_liquidity_fee_collects_fees_for_range_order() {
Ok(PoolInfo {
limit_order_fee_hundredth_pips: old_fee,
range_order_fee_hundredth_pips: old_fee,
range_order_total_fees_earned: Default::default(),
limit_order_total_fees_earned: Default::default(),
range_total_swap_inputs: Default::default(),
limit_total_swap_inputs: Default::default(),
})
);

Expand Down Expand Up @@ -1062,3 +1078,42 @@ fn asset_conversion() {
);
});
}

#[test]
fn fees_are_getting_recorded() {
new_test_ext().execute_with(|| {
let range_1 = -100..100;

// Create a new pool.
assert_ok!(LiquidityPools::new_pool(
RuntimeOrigin::root(),
Asset::Eth,
STABLE_ASSET,
Default::default(),
price_at_tick(0).unwrap(),
));

assert_ok!(LiquidityPools::set_range_order(
RuntimeOrigin::signed(ALICE),
Asset::Eth,
STABLE_ASSET,
0,
Some(range_1.clone()),
RangeOrderSize::Liquidity { liquidity: 100_000 },
));

MockBalance::assert_fees_recorded(&ALICE);

assert_ok!(LiquidityPools::set_limit_order(
RuntimeOrigin::signed(BOB),
Asset::Eth,
STABLE_ASSET,
Order::Sell,
6,
Some(100),
700_000,
));

MockBalance::assert_fees_recorded(&BOB);
});
}
11 changes: 11 additions & 0 deletions state-chain/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1285,9 +1285,20 @@ impl_runtime_apis! {
(asset, pallet_cf_lp::FreeBalances::<Runtime>::get(&account_id, asset).unwrap_or(0))
).collect();

let mut earned_fees: BTreeMap<_, _> = BTreeMap::new();

for asset in Asset::all() {
let earned_fees_for_asset = pallet_cf_lp::HistoricalEarnedFees::<Runtime>::get(&account_id, asset);
earned_fees
.entry(asset.into())
.and_modify(|assets: &mut Vec<_>| assets.push((asset, earned_fees_for_asset)))
.or_insert(vec![(asset, earned_fees_for_asset)]);
}

Some(LiquidityProviderInfo {
refund_addresses,
balances,
earned_fees,
})
}

Expand Down
1 change: 1 addition & 0 deletions state-chain/runtime/src/runtime_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub struct AuctionState {
pub struct LiquidityProviderInfo {
pub refund_addresses: Vec<(ForeignChain, Option<ForeignChainAddress>)>,
pub balances: Vec<(Asset, AssetAmount)>,
pub earned_fees: BTreeMap<ForeignChain, Vec<(Asset, AssetAmount)>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the new any::AssetMap?

}

#[derive(Debug, Decode, Encode, TypeInfo)]
Expand Down
Loading
Loading