Skip to content

Commit

Permalink
feat: Add Validator/NodeClient MegaVault methods (#300)
Browse files Browse the repository at this point in the history
Adds gRPC-based MegaVault methods. Tests and examples included.
Also extends a bit the `BigIntExt` trait to convert `BigInt` into our
serialized integer arrays.

Closes #297.
  • Loading branch information
v0-e authored Nov 29, 2024
1 parent f2daa56 commit 91f2ea8
Show file tree
Hide file tree
Showing 6 changed files with 371 additions and 3 deletions.
76 changes: 76 additions & 0 deletions v4-client-rs/client/examples/validator_megavault.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
mod support;
use anyhow::{Error, Result};
use bigdecimal::num_bigint::BigInt;
use dydx::config::ClientConfig;
use dydx::node::{BigIntExt, NodeClient, Wallet};
use support::constants::TEST_MNEMONIC;
use tokio::time::{sleep, Duration};

pub struct MegaVaulter {
client: NodeClient,
wallet: Wallet,
}

impl MegaVaulter {
pub async fn connect() -> Result<Self> {
let config = ClientConfig::from_file("client/tests/testnet.toml").await?;
let client = NodeClient::connect(config.node).await?;
let wallet = Wallet::from_mnemonic(TEST_MNEMONIC)?;
Ok(Self { client, wallet })
}
}

#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt().try_init().map_err(Error::msg)?;
#[cfg(feature = "telemetry")]
support::telemetry::metrics_dashboard().await?;

let mut vaulter = MegaVaulter::connect().await?;
let mut account = vaulter.wallet.account(0, &mut vaulter.client).await?;
let address = account.address().clone();
let subaccount = account.subaccount(0)?;

// Deposit 1 USDC into the MegaVault
let tx_hash = vaulter
.client
.megavault()
.deposit(&mut account, subaccount.clone(), 1)
.await?;
tracing::info!("Deposit transaction hash: {:?}", tx_hash);

sleep(Duration::from_secs(2)).await;

// Withdraw 1 share from the MegaVault
let number_of_shares: BigInt = 1.into();
let tx_hash = vaulter
.client
.megavault()
.withdraw(&mut account, subaccount, 0, Some(&number_of_shares))
.await?;
tracing::info!("Withdraw transaction hash: {:?}", tx_hash);

// Query methods

let owner_shares = vaulter
.client
.megavault()
.get_owner_shares(&address)
.await?;
tracing::info!("Get owner shares: {owner_shares:?}");

// Convert serialized integer into an integer (`BigIntExt` trait)
if let Some(shares) = owner_shares.shares {
let nshares = BigInt::from_serializable_int(&shares.num_shares)?;
tracing::info!("Number of owned shares: {}", nshares);
}

let withdrawal_info = vaulter
.client
.megavault()
.get_withdrawal_info(&number_of_shares)
.await?;
tracing::info!("Get withdrawal info: {withdrawal_info:?}");

Ok(())
}
139 changes: 139 additions & 0 deletions v4-client-rs/client/src/node/client/megavault.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use super::*;
use crate::node::utils::BigIntExt;

use anyhow::{anyhow as err, Error};
use dydx_proto::dydxprotocol::{
subaccounts::SubaccountId,
vault::{
MsgDepositToMegavault, MsgWithdrawFromMegavault, NumShares,
QueryMegavaultOwnerSharesRequest, QueryMegavaultOwnerSharesResponse,
QueryMegavaultWithdrawalInfoRequest, QueryMegavaultWithdrawalInfoResponse,
},
};

use bigdecimal::num_bigint::ToBigInt;

/// [`NodeClient`] MegaVault requests dispatcher
pub struct MegaVault<'a> {
client: &'a mut NodeClient,
}

impl<'a> MegaVault<'a> {
/// Create a new MegaVault requests dispatcher
pub(crate) fn new(client: &'a mut NodeClient) -> Self {
Self { client }
}

/// Deposit USDC into the MegaVault.
///
/// Check [the example](https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/validator_megavault.rs).
pub async fn deposit(
&mut self,
account: &mut Account,
subaccount: Subaccount,
amount: impl Into<Usdc>,
) -> Result<TxHash, NodeError> {
let client = &mut self.client;

let subaccount_id = SubaccountId {
owner: subaccount.address.to_string(),
number: subaccount.number.0,
};
let quantums = amount
.into()
.quantize()
.to_bigint()
.ok_or_else(|| err!("Failed converting USDC quantums to BigInt"))?
.to_serializable_vec()?;

let msg = MsgDepositToMegavault {
subaccount_id: Some(subaccount_id),
quote_quantums: quantums,
};

let tx_raw = client.create_transaction(account, msg).await?;

client.broadcast_transaction(tx_raw).await
}

/// Withdraw shares from the MegaVault.
/// The number of shares must be equal or greater to some specified minimum amount (in
/// USDC-equivalent value).
///
/// Check [the example](https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/validator_megavault.rs).
pub async fn withdraw(
&mut self,
account: &mut Account,
subaccount: Subaccount,
min_amount: impl Into<Usdc>,
shares: Option<&BigInt>,
) -> Result<TxHash, NodeError> {
let client = &mut self.client;

let subaccount_id = SubaccountId {
owner: subaccount.address.to_string(),
number: subaccount.number.0,
};
let quantums = min_amount
.into()
.quantize()
.to_bigint()
.ok_or_else(|| err!("Failed converting USDC quantums to BigInt"))?
.to_serializable_vec()?;
let num_shares = shares
.map(|x| x.to_serializable_vec())
.transpose()?
.map(|vec| NumShares { num_shares: vec });

let msg = MsgWithdrawFromMegavault {
subaccount_id: Some(subaccount_id),
min_quote_quantums: quantums,
shares: num_shares,
};

let tx_raw = client.create_transaction(account, msg).await?;

client.broadcast_transaction(tx_raw).await
}

/// Query the shares associated with an [`Address`].
///
/// Check [the example](https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/validator_megavault.rs).
pub async fn get_owner_shares(
&mut self,
address: &Address,
) -> Result<QueryMegavaultOwnerSharesResponse, Error> {
let client = &mut self.client;
let req = QueryMegavaultOwnerSharesRequest {
address: address.to_string(),
};

let response = client.vault.megavault_owner_shares(req).await?.into_inner();

Ok(response)
}

/// Query the withdrawal information for a specified number of shares.
///
/// Check [the example](https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/validator_megavault.rs).
pub async fn get_withdrawal_info(
&mut self,
shares: &BigInt,
) -> Result<QueryMegavaultWithdrawalInfoResponse, Error> {
let client = &mut self.client;
let num_shares = NumShares {
num_shares: shares.to_serializable_vec()?,
};
let req = QueryMegavaultWithdrawalInfoRequest {
shares_to_withdraw: Some(num_shares),
};

let response = client
.vault
.megavault_withdrawal_info(req)
.await?
.into_inner();

Ok(response)
}
}
13 changes: 12 additions & 1 deletion v4-client-rs/client/src/node/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub mod error;
mod megavault;
mod methods;

use super::{
builder::TxBuilder, config::NodeConfig, order::*, sequencer::*, utils::*, wallet::Account,
};
use megavault::MegaVault;

pub use crate::indexer::{Address, ClientId, Height, OrderFlags, Subaccount, Tokenized, Usdc};
use anyhow::{anyhow as err, Error, Result};
Expand Down Expand Up @@ -42,6 +44,7 @@ use dydx_proto::{
sending::{MsgCreateTransfer, MsgDepositToSubaccount, MsgWithdrawFromSubaccount, Transfer},
stats::query_client::QueryClient as StatsClient,
subaccounts::query_client::QueryClient as SubaccountsClient,
vault::query_client::QueryClient as VaultClient,
},
ToAny,
};
Expand Down Expand Up @@ -106,6 +109,8 @@ pub struct Routes {
pub subaccounts: SubaccountsClient<Timeout<Channel>>,
/// Tx utilities for the Cosmos SDK.
pub tx: TxClient<Timeout<Channel>>,
/// Vaults
pub vault: VaultClient<Timeout<Channel>>,
}

impl Routes {
Expand All @@ -124,7 +129,8 @@ impl Routes {
staking: StakingClient::new(channel.clone()),
stats: StatsClient::new(channel.clone()),
subaccounts: SubaccountsClient::new(channel.clone()),
tx: TxClient::new(channel),
tx: TxClient::new(channel.clone()),
vault: VaultClient::new(channel),
}
}
}
Expand Down Expand Up @@ -500,6 +506,11 @@ impl NodeClient {
Ok(Some(tx_hash))
}

/// Access the vaults requests dispatcher
pub fn megavault(&mut self) -> MegaVault {
MegaVault::new(self)
}

/// Simulate a transaction.
///
/// Check [the example](https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/withdraw_other.rs).
Expand Down
1 change: 1 addition & 0 deletions v4-client-rs/client/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ pub use client::{error::*, Address, NodeClient, Subaccount, TxHash};
pub use config::NodeConfig;
pub use order::*;
pub use types::ChainId;
pub use utils::BigIntExt;
pub use wallet::{Account, Wallet};
85 changes: 83 additions & 2 deletions v4-client-rs/client/src/node/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use bigdecimal::num_bigint::{BigInt, Sign};

/// An extension trait for [`BigInt`].
pub trait BigIntExt {
/// Initialize a heap-allocated big integer from a bytes slice.
/// Initialize a [`BigInt`] from a bytes slice.
fn from_serializable_int(bytes: &[u8]) -> Result<BigInt, Error>;
/// Creates a bytes vector from a [`BigInt`].
fn to_serializable_vec(&self) -> Result<Vec<u8>, Error>;
}

impl BigIntExt for BigInt {
Expand All @@ -21,6 +23,23 @@ impl BigIntExt for BigInt {

Ok(BigInt::from_bytes_be(sign, &bytes[1..]))
}

fn to_serializable_vec(&self) -> Result<Vec<u8>, Error> {
if self.sign() == Sign::NoSign {
return Ok(vec![0x02]);
}

let (sign, bytes) = self.to_bytes_be();
let mut vec = vec![0; 1 + bytes.len()];

vec[0] = match sign {
Sign::Plus | Sign::NoSign => 2,
Sign::Minus => 3,
};
vec[1..].copy_from_slice(&bytes);

Ok(vec)
}
}

#[cfg(test)]
Expand All @@ -29,7 +48,7 @@ mod tests {
use std::str::FromStr;

#[test]
fn node_utils_to_bigint() -> Result<(), Error> {
fn node_utils_serializable_to_bigint() -> Result<(), Error> {
assert_eq!(
BigInt::from_str("0")?,
BigInt::from_serializable_int(&[0x02])?
Expand Down Expand Up @@ -93,4 +112,66 @@ mod tests {

Ok(())
}

#[test]
fn node_utils_serializable_from_bigint() -> Result<(), Error> {
assert_eq!(
[0x02].to_vec(),
BigInt::from_str("0")?.to_serializable_vec()?,
);
assert_eq!(
[0x02].to_vec(),
BigInt::from_str("-0")?.to_serializable_vec()?,
);
assert_eq!(
[0x02, 0x01].to_vec(),
BigInt::from_str("1")?.to_serializable_vec()?,
);
assert_eq!(
[0x03, 0x01].to_vec(),
BigInt::from_str("-1")?.to_serializable_vec()?,
);
assert_eq!(
[0x02, 0xFF].to_vec(),
BigInt::from_str("255")?.to_serializable_vec()?,
);
assert_eq!(
[0x03, 0xFF].to_vec(),
BigInt::from_str("-255")?.to_serializable_vec()?,
);
assert_eq!(
[0x02, 0x01, 0x00].to_vec(),
BigInt::from_str("256")?.to_serializable_vec()?,
);
assert_eq!(
[0x03, 0x01, 0x00].to_vec(),
BigInt::from_str("-256")?.to_serializable_vec()?,
);
assert_eq!(
[0x02, 0x07, 0x5b, 0xcd, 0x15].to_vec(),
BigInt::from_str("123456789")?.to_serializable_vec()?,
);
assert_eq!(
[0x03, 0x07, 0x5b, 0xcd, 0x15].to_vec(),
BigInt::from_str("-123456789")?.to_serializable_vec()?,
);
assert_eq!(
[0x02, 0x01, 0xb6, 0x9b, 0x4b, 0xac, 0xd0, 0x5f, 0x15].to_vec(),
BigInt::from_str("123456789123456789")?.to_serializable_vec()?,
);
assert_eq!(
[0x03, 0x01, 0xb6, 0x9b, 0x4b, 0xac, 0xd0, 0x5f, 0x15].to_vec(),
BigInt::from_str("-123456789123456789")?.to_serializable_vec()?,
);
assert_eq!(
[0x02, 0x66, 0x1e, 0xfd, 0xf2, 0xe3, 0xb1, 0x9f, 0x7c, 0x04, 0x5f, 0x15].to_vec(),
BigInt::from_str("123456789123456789123456789")?.to_serializable_vec()?,
);
assert_eq!(
[0x03, 0x66, 0x1e, 0xfd, 0xf2, 0xe3, 0xb1, 0x9f, 0x7c, 0x04, 0x5f, 0x15].to_vec(),
BigInt::from_str("-123456789123456789123456789")?.to_serializable_vec()?,
);

Ok(())
}
}
Loading

0 comments on commit 91f2ea8

Please sign in to comment.