-
Notifications
You must be signed in to change notification settings - Fork 88
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
Pallet-Investments #909
Pallet-Investments #909
Conversation
This PR solely contains the pallet-investments. Currently, no logic from pallet-pools is removed and replaced by the pallet to make this PR easier to be reviewed. |
the following approach would work, but it does not as currently parity does not take over all trait bounds on all implementations on Pallet<T>. So I wanna have this state stored and the next commit will revert it to a state that does actually compile. Once, paritytech/substrate#12053 is resolved, we can simply revert the next commit and should be fine.
dc0a491
to
dea0426
Compare
This should be ready for review. Contains just the basic business logic for investing. No tests, benchmarks, weights. No integrations. |
@branan the Cargo.lock file is insanely changed. I am not sure why. Do you have an idea? Seems like cargo define a really specific version for many substrate devs, which I am not sure why. Bet I have a misconfig in the pallets Cargo.toml. ^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving a first set of minor comments, still trying to think through the approach more broadly.
libs/common-traits/src/lib.rs
Outdated
|
||
pub trait InvestmentManager { | ||
type Error; | ||
type AssetId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering whether Asset is the best name for this. It's at least conflicting with the use of assets in the loans pallet: https://github.com/centrifuge/centrifuge-chain/blob/parachain/pallets/loans/src/types.rs#L23
(the alternative fix could be to rename it in the loans pallet though, and standardize using assets going forward for this use case. But of course it also conflicts with the assets pallet)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, definitely not a good one. What about InvestmentId
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems like a simple solution, yes!
pallets/investments/src/lib.rs
Outdated
|
||
/// The outstanding collections for an account | ||
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] | ||
pub struct Collection<Balance> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The naming of these fields is much better than what we had before 👍
pallets/investments/src/lib.rs
Outdated
asset: AssetId, | ||
amount: Amount, | ||
}, | ||
Redeemption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redeemption { | |
Redemption { |
pallets/investments/src/lib.rs
Outdated
order: &mut OrderOf<T>, | ||
amount: T::Amount, | ||
) -> DispatchResult { | ||
let asset_account = AssetAccount { asset_id }.into_account_truncating(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, all tokens are stored in the PoolId
derived account. I think we should keep it this way. The advantage of this is that (once Subscan properly supports token balances, which it should in the short term), you can easily see the entire balance of a pool on one account: the currency it holds, the tranche tokens, and all the collateral NFTs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes Sense. I will adapt the asset management trait accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait. This is not for the pools to hold. It is basically the "in-between", when the pool has not yet signaled it will want to get investments. I think it makes more sense, to keep the "in-between" in this pallet.
Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, you are right. THis should be pools. Although, it will eventually show inconsistent state then about "how much does the pool actually have". As it will contain orders that are not fulfilled, and might never be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about we transfer the tokens after the pool has signaled how much he wants to fulfill?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't that how it currently works? As in, my understanding would be: on epoch execution, the pool pallet mints the executed tranche tokens for investments into the pool account. This is the moment when the pool signals to the investments pallet what the fulfillment was, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. The way it currently works is that the pool account holds tokens for the following four things:
- Tokens transferred from investors as part of an invest or redeem
- Tokens yet to be claimed by investors after a successful investment / redemption
- Tokens that have been repaid by borrowers but are on-hold until an epoch close
- Tokens that are available for borrowers to borrow
The separation between those categories is handled entirely by the internal logic of the pools pallet (and is most of what the logic is about).
We could probably rejigger where some of that is stored so that it's clearer in tools like subscan what the state of the tokens is, but that adds extra transfer logic to lots of places where we currently just update a couple integers.
… derived account in our case.
pallets/investments/Cargo.toml
Outdated
sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } | ||
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } | ||
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } | ||
orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "master" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the line that dragged in a different version of polkadot. You want the polkadot-v0.9.24
branch of ORML here.
pallets/investments/Cargo.toml
Outdated
[dev-dependencies] | ||
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } | ||
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } | ||
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "master" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above for ORML versioning
pallets/investments/src/lib.rs
Outdated
>; | ||
|
||
#[pallet::type_value] | ||
pub fn OnTotalOrderEmpty<T: Config>() -> TotalOrder<T::Amount> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why a custom value here instead of giving TotalOrder a Default
impl for the ValueQuery
to use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good Idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, done!
pallets/investments/src/lib.rs
Outdated
)?; | ||
|
||
// drain the orders that have been collected | ||
orders.retain(|order| !collected.contains(&order.id)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this is O(n^2) worst case, if we collect all orders.
I think the for loop above could be turned into the retain call, and we do the collection and removal all in one iteration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I logically made it like this. But because rust can not return early from these iterators it is still a for loop but O(2*n) now.
pallets/investments/src/lib.rs
Outdated
CollectType::Overflowing => { | ||
ActiveOrder::<T>::try_mutate(&asset_id, |total_order| -> DispatchResult { | ||
if collection.remaining_asset_invest > T::Amount::zero() { | ||
Orders::<T>::try_mutate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't we already in a try_mutate
block for orders here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, corrected this!
pallets/investments/src/lib.rs
Outdated
}; | ||
|
||
if collection.remaining_asset_redeem > T::Amount::zero() { | ||
Orders::<T>::try_mutate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, this looks like a nested try_mutate
of the same storage item
|
||
/// A possible check if investors fulfill every condition to invest into a | ||
/// given asset | ||
type PreConditions: PreConditions< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PreConditions -> Preconditions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rename the trait to then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some things that look like real bugs,so I'm flagging as request changes
for now. But I'm broadly 👍 on this. It looks nice and clean and like a good separation of the investment logic from the current pools implementation to this.
I addressed all issues and also correctes some logical bugs that where happening when fulfilment was called, as no transfer to the pool happened. Logic now can be seen from the pool side as:
This leads to the pool always having the amount that is actually invested on his balance sheet. The rest is always in the pallet-investments. |
Seems to be nitpick, but can we use doc comments Otherwise, have not much knowledge yet to comment on how it would affect the code base. |
Totally. I will add some docs in the next issues. Won't make it this week to this and don't wanna go through review again. |
libs/common-traits/src/lib.rs
Outdated
) -> Result<(), Self::Error>; | ||
} | ||
|
||
pub trait AssetAccountant<AccountId> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also rename this to InvestmentAccountant
, and the AssetId
here to InvestmentId
, and AssetInfo
to InvestmentInfo
, etc? Otherwise we still have the issue about the unclarity of the definition of an asset.
pallets/investments/src/lib.rs
Outdated
.into_account_truncating(); | ||
let info = T::Accountant::info(asset_id.clone())?; | ||
|
||
if invest >= redeem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this not be invest > redeem
? If invest == redeem
, then there's nothing to transfer.
pallets/investments/src/lib.rs
Outdated
Pallet::<T>::acc_payout_invest(&mut collection, &fulfillment, &order)?; | ||
Pallet::<T>::acc_payout_redeem(&mut collection, &fulfillment, &order)?; | ||
Pallet::<T>::acc_remaining_invest(&mut collection, &fulfillment, &order)?; | ||
Pallet::<T>::acc_remaining_invest(&mut collection, &fulfillment, &order)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pallet::<T>::acc_remaining_invest(&mut collection, &fulfillment, &order)?; | |
Pallet::<T>::acc_remaining_redeem(&mut collection, &fulfillment, &order)?; |
TODO: Change InvestementManager logic to also split invest/tedeem - Add a Subscruber pattern to fulfillment
- only one processing order concurrently possible - remove orderID from trait - docs for traits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing new jumps out as weird/wrong to me, so I think this is ready to land
Thanks a bunch for updating nix. I think it's getting time for me to set this up too... |
I will enable auto-merge then. |
The investment pallet that should take care of:
Providing orders for assets
allowing users to collect these orders
Implementation Note
The
AssetManager
implementation should implement for a specific asset, while the AssetId in the pallet-investments can get an enum ofpub enum AssetId<PoolId, TrancheId>{
Pool(PoolId, TrancheLoc)
}