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(vault): Lend Token collateral #447

Merged
merged 14 commits into from
Feb 22, 2023

Conversation

daniel-savu
Copy link
Contributor

@daniel-savu daniel-savu commented Feb 10, 2023

Adds the ability to run vaults with a lend token currency (e.g. QKSM, QUSDT). The client reads current lending markets from storage to determine the (underlying, lend_token) pairs and then listens for NewMarketEvent and UpdatedMarketEvent to keep the pairs up-to-date.

After some investigation with @sander2, it turns out that all keys from storage maps that use the Blake2_128Concat hasher have a 48 byte prefix than needs to be removed to decode the key. This can replace the way keys are decoded throughout the repo (for instance, in get_foreign_assets_metadata), and doing that would also allow for reusing a single key-decoding function.

This PR was tested on wss://kintnet-api.interlay.io/parachain with QKSM, QUSDT and QKBTC to ensure the client succesfully registers the vault and generates a bitcoin wallet.

Closes: #450

@daniel-savu daniel-savu marked this pull request as draft February 10, 2023 17:48
@daniel-savu daniel-savu changed the title WIP(vault): Lend Token collateral feat(vault): Lend Token collateral Feb 16, 2023
@daniel-savu daniel-savu marked this pull request as ready for review February 16, 2023 13:13
runtime/src/rpc.rs Outdated Show resolved Hide resolved
@@ -83,6 +150,11 @@ impl TryFromSymbol for CurrencyId {
id if id == KSM.symbol() => Ok(Token(KSM)),
id if id == KBTC.symbol() => Ok(Token(KBTC)),
id if id == KINT.symbol() => Ok(Token(KINT)),
// Does the first character match the lend token prefix?
id if id.chars().next() == Some(LEND_TOKEN_SYMBOL_PREFIX) => {
Copy link
Member

Choose a reason for hiding this comment

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

The unfortunate problem with this is that parsing would fail if we were to register any other asset which begins with Q, we should somehow always fall-through to get_foreign_asset_by_symbol.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Found a pretty neat solution using an if-let match guard. This feature is experimental however - no bugs have been reported but it's just not well-documented enough: rust-lang/rust#51114 (the issue thread was active within the last month).

const LEND_TOKEN_SYMBOL_PREFIX: char = 'Q';
/// The name of Lend Tokens is the name of their underlying currency,
/// prefixed by `Lend`. Example: `LendPolkadot` is the `Polkadot` lend token name.
const LEND_TOKEN_NAME_PREFIX: &str = "Lend";
Copy link
Member

Choose a reason for hiding this comment

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

Is the name used anywhere? The symbol is used parsing at startup, but just wondering if we also need to add the prefix if the name() is used.

Copy link
Contributor Author

@daniel-savu daniel-savu Feb 16, 2023

Choose a reason for hiding this comment

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

It's used to make Coingecko API calls, so not directly relevant but thought it's better to have an implementation than to panic

But we have the coingecko_id for that..? Also, why is coingecko used at all? Or is it within the oracle?

runtime/src/assets.rs Outdated Show resolved Hide resolved
/// `twox_128("PalletName") ++ twox_128("ItemName") ++ Blake2_128Concat(key) ++ key`
/// So to get the actual value of the key from a raw key, we need to ignore the first
/// `3 * 128 / 8` bytes, or `48` bytes.
const STORAGE_KEY_HASH_PREFIX_LENGTH: usize = 48;
Copy link
Member

Choose a reason for hiding this comment

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

can you also use this for the existing decodings?

/// Keys in storage maps are prefixed by two `twox_128` hashes: the pallet name and the
/// storage item names. Then, assuming the map uses the `Blake2_128Concat` hasher, the layout
/// looks as follows:
/// `twox_128("PalletName") ++ twox_128("ItemName") ++ Blake2_128Concat(key) ++ key`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// `twox_128("PalletName") ++ twox_128("ItemName") ++ Blake2_128Concat(key) ++ key`
/// `twox_128("PalletName") ++ twox_128("ItemName") ++ Blake2(key) ++ key`

Nit: I think Blake2_128Concat means Blake2(key) ++ key so key would be in there twice

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Turns out that AssetRegistry::Metadata is hashed with Twox64Concat :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a separate decoding function for that hasher


pub(crate) fn insert(underlying_id: CurrencyId, lend_token_id: CurrencyId) -> Result<(), Error> {
log::info!(
"Found loans market: {:?}, with lend token: {:?}",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI this prints the full struct rather than the ticker, because the concurrent listener might run before the foreign asset listener

@daniel-savu daniel-savu requested a review from sander2 February 20, 2023 16:25
@@ -663,24 +790,54 @@ impl UtilFuncs for InterBtcParachain {
&vault_id.account_id == self.get_account_id()
}

async fn get_foreign_assets_metadata(&self) -> Result<Vec<(u32, AssetMetadata)>, Error> {
async fn get_decoded_storage_keys<T, U, F>(
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this should be a trait function - I think this should be a private function in the impl InterBtcParachain, just like other internal utility functions like with_unique_signer

))]
async fn get_lend_tokens(&self) -> Result<Vec<(CurrencyId, CurrencyId)>, Error>;

async fn get_decoded_storage_keys<T, U, F>(
Copy link
Member

Choose a reason for hiding this comment

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

I'd probably not have this take a callback function, but rather an enum and have the logic inside this function. Not a huge deal if you want to keep the callback, but if so, please make it return a Result, since &raw_key[BLAKE2_128_HASH_PREFIX_LENGTH..] will panic if the input slice is shorter than expected

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean an enum with the hasher type of the storage key?

Copy link
Member

Choose a reason for hiding this comment

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

yes

@sander2 sander2 merged commit 1f871fd into interlay:master Feb 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lend Token Vault Support
3 participants