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

Pallet-Investments #909

Merged
merged 25 commits into from
Sep 6, 2022
Merged

Pallet-Investments #909

merged 25 commits into from
Sep 6, 2022

Conversation

mustermeiszer
Copy link
Collaborator

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 of
pub enum AssetId<PoolId, TrancheId>{
Pool(PoolId, TrancheLoc)
}

@mustermeiszer
Copy link
Collaborator Author

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.
A follow-up PR will integrate the pallet into our existing pool.

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.
@mustermeiszer mustermeiszer marked this pull request as ready for review August 17, 2022 13:13
@mustermeiszer mustermeiszer force-pushed the feature/pallet-investments-v2 branch from dc0a491 to dea0426 Compare August 17, 2022 13:15
@mustermeiszer
Copy link
Collaborator Author

This should be ready for review. Contains just the basic business logic for investing. No tests, benchmarks, weights. No integrations.

@mustermeiszer
Copy link
Collaborator Author

@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.

^^

Copy link
Contributor

@hieronx hieronx left a 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.


pub trait InvestmentManager {
type Error;
type AssetId;
Copy link
Contributor

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)

Copy link
Collaborator Author

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?

Copy link
Contributor

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!


/// The outstanding collections for an account
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct Collection<Balance> {
Copy link
Contributor

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 👍

asset: AssetId,
amount: Amount,
},
Redeemption {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Redeemption {
Redemption {

order: &mut OrderOf<T>,
amount: T::Amount,
) -> DispatchResult {
let asset_account = AssetAccount { asset_id }.into_account_truncating();
Copy link
Contributor

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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?

Copy link
Contributor

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?

Copy link
Contributor

@branan branan Aug 18, 2022

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.

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" }
Copy link
Contributor

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.

[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" }
Copy link
Contributor

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

>;

#[pallet::type_value]
pub fn OnTotalOrderEmpty<T: Config>() -> TotalOrder<T::Amount>
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good Idea!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea, done!

)?;

// drain the orders that have been collected
orders.retain(|order| !collected.contains(&order.id));
Copy link
Contributor

@branan branan Aug 18, 2022

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?

Copy link
Collaborator Author

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.

CollectType::Overflowing => {
ActiveOrder::<T>::try_mutate(&asset_id, |total_order| -> DispatchResult {
if collection.remaining_asset_invest > T::Amount::zero() {
Orders::<T>::try_mutate(
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, corrected this!

};

if collection.remaining_asset_redeem > T::Amount::zero() {
Orders::<T>::try_mutate(
Copy link
Contributor

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<
Copy link
Contributor

Choose a reason for hiding this comment

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

PreConditions -> Preconditions

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Why? ^^

Copy link
Collaborator Author

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?

Copy link
Contributor

@branan branan left a 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.

@mustermeiszer
Copy link
Collaborator Author

mustermeiszer commented Aug 19, 2022

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:

  • Invest x into InvestmendId
  • Transfer x of payment_currency to account for that InvestmendId (pallet-investment derived)
  • Redeem y from InvestmendId
  • Transfer y of InvestmentId to account for that InvestmentId (pallet-investment derived)
  • Pool fetches investments
  • Pool fulfills x and y 100% (or less of course) and calls fulfillment with price p
  • Investment transfers (assuming: higher invest flow than redeem flow of payment_currency ) x - y*p of payment_currency from account for that InvestmentId to the payment_account for that InvestmentId (i.e. the pool derived one)
  • Investment tells pool to mint x/p tokens to the account for that InvestmentId

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.

@gruberb
Copy link
Contributor

gruberb commented Aug 23, 2022

Seems to be nitpick, but can we use doc comments/// above structs and traits? Would help getting familiar with what the business implications etc are and how to use certain things!

Otherwise, have not much knowledge yet to comment on how it would affect the code base.

@mustermeiszer
Copy link
Collaborator Author

Seems to be nitpick, but can we use doc comments/// above structs and traits? Would help getting familiar with what the business implications etc are and how to use certain things!

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.

) -> Result<(), Self::Error>;
}

pub trait AssetAccountant<AccountId> {
Copy link
Contributor

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.

.into_account_truncating();
let info = T::Accountant::info(asset_id.clone())?;

if invest >= redeem {
Copy link
Contributor

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.

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)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Pallet::<T>::acc_remaining_invest(&mut collection, &fulfillment, &order)?;
Pallet::<T>::acc_remaining_redeem(&mut collection, &fulfillment, &order)?;

@mustermeiszer mustermeiszer added the crcl-runtime Circle runtime related. label Sep 2, 2022
Copy link
Contributor

@branan branan left a 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

@mustermeiszer
Copy link
Collaborator Author

Thanks a bunch for updating nix. I think it's getting time for me to set this up too...

@mustermeiszer mustermeiszer enabled auto-merge (squash) September 6, 2022 19:53
@mustermeiszer
Copy link
Collaborator Author

I will enable auto-merge then.

@mustermeiszer mustermeiszer merged commit 0d4ae2a into parachain Sep 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crcl-runtime Circle runtime related.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants