1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// Copyright 2021 ChainSafe Systems
// SPDX-License-Identifier: LGPL-3.0-only

//! This contains shared traits that are used in multiple pallets to prevent
//! circular dependencies

use crate::{AssetAvailability, AssetPricePair, AssetProportions, Price, Ratio};
use frame_support::{
	dispatch::DispatchError,
	sp_runtime::{app_crypto::sp_core::U256, DispatchResult},
	sp_std::result::Result,
};
use xcm::v1::MultiLocation;

/// Type that provides the mapping between `AssetId` and `MultiLocation`.
pub trait MultiAssetRegistry<AssetId> {
	/// Determines the relative location of the consensus system where the given
	/// asset is native from the point of view of the current system
	fn native_asset_location(asset: &AssetId) -> Option<MultiLocation>;

	/// Whether the given identifier is currently supported as a liquid asset
	fn is_liquid_asset(asset: &AssetId) -> bool;
}

/// Facility for remote asset operations.
pub trait RemoteAssetManager<AccountId, AssetId, Balance> {
	/// Transfers the given amount of asset from the account's sovereign account
	/// on PINT into the account on the asset's destination.
	///
	/// This performs the following steps:
	/// - Ensure the account has enough free balance of the given asset
	/// - Depending on the asset's location this will execute
	///     - an XCM InitiateReserveWithdraw followed by XCM Deposit order, if the location of the
	///       asset is a reserve location of PINT (Relay Chain)
	///     - an XCM InitiateReserveWithdraw followed by XCM DepositReserveAsset order will be
	///       dispatched as XCM ReserveAssetDeposit with an Xcm Deposit order
	fn transfer_asset(who: AccountId, asset: AssetId, amount: Balance) -> DispatchResult;

	/// Notification of deposited funds in the index, ready to be `bond` to earn staking rewards.
	///
	/// This is an abstraction over how staking is supported on the `asset`'s native location.
	/// In general, this can be one of
	///     - None, staking is not supported, meaning this asset is idle.
	///     - Staking via the FRAME `pallet_staking`, (e.g. Relay Chain).
	///     - Liquid Staking, with support for early unbonding.
	fn deposit(asset: AssetId, amount: Balance);

	/// Notification of an upcoming withdrawal.
	///
	/// This tells the remote asset manager to either reserve the given amount from the idle remote
	/// balance or prepare to unbond those active funds.
	///
	/// Unbonding an asset will involve:
	///     - Nothing for assets that do not support staking (always idle asset).
	///     - Call `pallet_staking::unbond` + `pallet_staking::withdraw` on the asset's native chain
	///       (e.g Relay Chain)
	///     - Execute the unbond mechanism of the liquid staking protocol
	fn announce_withdrawal(asset: AssetId, amount: Balance);
}

// Default implementation that does nothing
impl<AccountId, AssetId, Balance> RemoteAssetManager<AccountId, AssetId, Balance> for () {
	fn transfer_asset(_: AccountId, _: AssetId, _: Balance) -> DispatchResult {
		Ok(())
	}

	fn deposit(_: AssetId, _: Balance) {}

	fn announce_withdrawal(_: AssetId, _: Balance) {}
}

/// Abstracts net asset value (`NAV`) related calculations
pub trait NavProvider<AssetId: Clone, Balance>: SaftRegistry<AssetId, Balance> {
	/// Calculates the amount of index tokens that the given units of the asset
	/// are worth.
	///
	/// This is achieved by dividing the value of the given units by the index' `NAV` (the on chain
	/// price of a index token). The value, or volume, of all the `units` is determined by
	/// `value(units) = units * Price_asset` (see: `asset_net_value`), and since the `NAV`
	/// represents the per token value of the index token, the equivalent number of index token is
	/// `value(units) / NAV`.
	fn index_token_equivalent(asset: AssetId, units: Balance) -> Result<Balance, DispatchError>;

	/// Calculates the units of the given asset that the given number of
	/// index_tokens are worth.
	/// This is the reverse of `index_token_equivalent`.
	///
	/// This is calculated by determining the net value of the given amount of index tokens
	/// and dividing it by the current price of the `asset`:
	/// `units_asset = (NAV * index_tokens) / Price_asset`
	fn asset_equivalent(index_tokens: Balance, asset: AssetId) -> Result<Balance, DispatchError>;

	/// Returns the price of the asset relative to the `NAV` of the index token.
	///
	/// This is a price pair in the form of `base/quote` whereas `base` is the `NAV` of the index
	/// token and `quote` the current price for the asset:  `NAV / Price_asset`.
	///
	/// *Note:*
	/// The price (or the value of 1 unit) of an asset secured by SAFTs is determined by the
	/// total asset value secured by all SAFTs divided by the units held in the index, (see:
	/// [`SaftRegistry::net_saft_value`])
	fn relative_asset_price(asset: AssetId) -> Result<AssetPricePair<AssetId>, DispatchError>;

	/// Calculates the net value of the given units of the given asset.
	///
	/// If the asset is liquid then the net value of an asset is determined by
	/// multiplying the share price of the asset by the given amount.: `units *
	/// Price_asset`.
	///
	/// If the asset is secured by SAFTs then the net value is determined by the net value
	/// of the associated `SAFTRecords`, (see: [`SaftRegistry::net_saft_value`]).
	fn calculate_net_asset_value(asset: AssetId, units: Balance) -> Result<Balance, DispatchError>;

	/// Calculates the net value of the given units of the given *liquid* asset.
	///
	/// In contrast to `calculate_asset_net_value`, here it is not checked
	/// whether the specified asset is liquid, but it is expected that this is
	/// the case and it attempts to determine the net value using the asset's
	/// price feed.
	fn calculate_net_liquid_value(asset: AssetId, units: Balance) -> Result<Balance, DispatchError>;

	/// Calculates the net value of the given units of the given SAFT.
	///
	/// In contrast to `calculate_asset_net_value`, here it is not checked
	/// whether the specified asset is secured by SAFT.
	/// The net value is then determined by  [`SaftRegistry::net_saft_value`]
	fn calculate_net_saft_value(asset: AssetId, units: Balance) -> Result<Balance, DispatchError>;

	/// Calculates the net value of all liquid assets combined.
	///
	/// This is essentially the sum of the value of all liquid assets:
	/// `net_liquid_value(Asset_0) + net_liquid_value(Asset_1) ...`
	fn total_net_liquid_value() -> Result<U256, DispatchError>;

	/// Calculates the net value of all SAFT combined.
	///
	/// This is essentially the sum of the value of all SAFTs:
	/// `net_saft_value(Asset_0) + net_saft_value(Asset_1) ...`
	fn total_net_saft_value() -> Result<U256, DispatchError>;

	/// Calculates the net asset value of all the index tokens which is equal to the
	/// sum of the total value of all assets.
	///
	/// Since the `NAV` represents the per index token value, net value of all
	/// index tokens is the product of the `NAV` and the total supply of index
	/// tokens: `NAV * index_token_issuance`.
	/// Or Simplified:
	/// `total_net_liquid_value + total_net_saft_value`.
	fn total_net_asset_value() -> Result<U256, DispatchError>;

	/// Calculates the net value of the given liquid asset.
	///
	/// In contrast to `net_asset_value`, here it is not checked whether the
	/// specified asset is liquid.
	fn net_liquid_value(asset: AssetId) -> Result<Balance, DispatchError> {
		Self::calculate_net_liquid_value(asset.clone(), Self::asset_balance(asset))
	}

	/// Calculates the net value of the given asset that were contributed to the index.
	///
	/// The net value of an asset is determined by multiplying the share price
	/// of the asset by the amount deposited in the index.: `Price_asset * Index
	/// Deposit`
	fn net_asset_value(asset: AssetId) -> Result<Balance, DispatchError>;

	/// Calculates the `NAV` of the index token, consisting of liquid assets
	/// and SAFT.
	///
	/// This the *per token value* (value of a single unit of index token, or its
	/// on chain price)
	///
	/// The the NAV is calculated by dividing the total value of all the
	/// contributed assets by the total supply of index token:
	/// `NAV = (NAV_0 + NAV_1+ ... + NAV_n) / TotalSupply(PINT)`. where
	/// `NAV_n` is the net value of all shares of the specific asset that were
	/// contributed to the index (see `calculate_net_asset_value`). And the sum of all of them is
	/// the `total_asset_net_value`.
	/// *Note:* in contrast to the index' `NAV` (which is a *per token* value) all `NAV_n` are the
	/// total volume of the specific asset. For example if the index consists of two liquid assets
	/// `L1` and `L2` then the total formula is `NAV = ( L1_units * L1_price + L2_units * L2_price)
	/// / TotalSupply(PINT)` which is also equivalent to
	/// `(L1_units * L1_price / TotalSupply(PINT)) + (L2_units * L2_price / TotalSupply(PINT))`
	///
	/// This can be simplified to
	/// `NAV = (Liquid_net_value + SAFT_net_value) / Total Supply`,
	/// which is also `NAV = NAV_liquids + NAV_saft`.
	fn nav() -> Result<Price, DispatchError>;

	/// Returns the per token `NAV` of the index token split to (`liquid`, `saft`).
	///
	/// Summed up, both of them add up to the total nav [`NavProvider::nav`]
	fn navs() -> Result<(Price, Price), DispatchError> {
		Ok((Self::liquid_nav()?, Self::saft_nav()?))
	}

	/// Calculates the NAV of the index token solely for the liquid assets.
	/// This is a *per token value*: the value of a single unit of index token for the funds total
	/// liquid value.
	///
	/// Following the `total_nav` calculation, the `NAV_liquids` is determined
	/// by `NAV_liquids = NAV - (SAFT_net_value / TotalSupply(PINT))`
	/// Or simplified
	/// `NAV - NAV_saft`, which is  `Liquid_net_value / TotalSupply(PINT)`
	fn liquid_nav() -> Result<Price, DispatchError>;

	/// Calculates the NAV of the index token solely for the SAFT
	/// This is a *per token value*: the value of a single unit of index token for the funds total
	/// SAFT value.
	///
	/// Following `liquid_nav` calculation, this is determined by:
	/// `SAFT_net_value / TotalSupply(PINT)`
	fn saft_nav() -> Result<Price, DispatchError>;

	/// Returns the share of the asset in the total value of the index:
	/// `Asset Value / Total Net Asset Value`
	fn asset_proportion(asset: AssetId) -> Result<Ratio, DispatchError>;

	/// Returns the share of the liquid asset in the total value of all liquid assets in the index:
	/// `Asset Value / Liquid Net Asset Value`
	fn liquid_asset_proportion(asset: AssetId) -> Result<Ratio, DispatchError>;

	/// Returns the share of the asset in the total value of all SAFTs of the asset in the index:
	/// `Asset Value / SAFT Net Asset Value`
	fn saft_asset_proportion(asset: AssetId) -> Result<Ratio, DispatchError>;

	/// Returns the proportions for each asset in the index
	fn asset_proportions() -> Result<AssetProportions<AssetId>, DispatchError>;

	/// Returns the proportions for each liquid asset in total value of liquid assets in the index
	fn liquid_asset_proportions() -> Result<AssetProportions<AssetId>, DispatchError>;

	/// Returns the proportions for each saft asset in total value of SAFTs in the index
	fn saft_asset_proportions() -> Result<AssetProportions<AssetId>, DispatchError>;

	/// The total supply of index tokens currently in circulation.
	fn index_token_issuance() -> Balance;

	/// The amount of the given asset currently held in the index.
	fn asset_balance(asset: AssetId) -> Balance;
}

/// Abstracts SAFT related information
pub trait SaftRegistry<AssetId, Balance> {
	/// Returns the value of the assets currently secured by the SAFTS
	fn net_saft_value(asset: AssetId) -> Balance;
}

/// Abstract core features of the `AssetIndex` shared across pallets.
pub trait AssetRecorder<AccountId, AssetId, Balance> {
	/// Add an liquid asset into the index.
	/// This moves the given units from the caller's balance into the index's
	/// and issues PINT accordingly.
	fn add_liquid(caller: &AccountId, id: AssetId, units: Balance, nav: Balance) -> DispatchResult;

	/// Mints the SAFT into the index and awards the caller with given amount of
	/// PINT token.
	/// If an asset with the given AssetId does not already
	/// exist, it will be registered as SAFT. Fails if the availability of
	/// the asset is liquid.
	fn add_saft(caller: &AccountId, id: AssetId, units: Balance, nav: Balance) -> DispatchResult;

	/// Sets the availability of the given asset.
	/// If the asset was already registered, the old `AssetAvailability` is
	/// returned.
	fn insert_asset_availability(asset_id: AssetId, availability: AssetAvailability) -> Option<AssetAvailability>;

	/// Dispatches transfer to move liquid assets out of the index’s account.
	/// Updates the index by burning the given amount of index token from
	/// the caller's account.
	fn remove_liquid(
		who: &AccountId,
		id: AssetId,
		units: Balance,
		nav: Balance,
		recipient: Option<AccountId>,
	) -> DispatchResult;

	/// Burns the given amount of SAFT token from the index and
	/// the nav from the caller's account
	fn remove_saft(who: &AccountId, id: AssetId, units: Balance, nav: Balance) -> DispatchResult;
}

/// Helper trait for runtime benchmarks
#[cfg(feature = "runtime-benchmarks")]
pub trait AssetRecorderBenchmarks<AssetId, Balance> {
	fn add_asset(asset_id: AssetId, units: Balance, location: MultiLocation, amount: Balance) -> DispatchResult;

	fn deposit_saft_equivalent(saft_nav: Balance) -> DispatchResult;
}

/// This is a helper trait only used for constructing `AssetId` types in Runtime Benchmarks
pub trait MaybeAssetIdConvert<A, B>: Sized {
	#[cfg(feature = "runtime-benchmarks")]
	fn try_convert(value: A) -> Option<B>;
}

#[cfg(feature = "runtime-benchmarks")]
impl<T> MaybeAssetIdConvert<u8, crate::types::AssetId> for T {
	fn try_convert(value: u8) -> Option<crate::types::AssetId> {
		frame_support::sp_std::convert::TryFrom::try_from(value).ok()
	}
}

#[cfg(not(feature = "runtime-benchmarks"))]
impl<T, A, B> MaybeAssetIdConvert<A, B> for T {}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::AssetId;

	fn assert_maybe_from<T: MaybeAssetIdConvert<u8, AssetId>>() {}

	#[test]
	fn maybe_from_works() {
		assert_maybe_from::<()>();
	}
}