mod env;
use env::TestEnv;
use anyhow::{anyhow as err, Error};
use bigdecimal::{num_traits::cast::ToPrimitive, BigDecimal, One};
use chrono::{TimeDelta, Utc};
use dydx::{
indexer::{Denom, OrderExecution, Ticker, Token},
node::*,
};
use dydx_proto::dydxprotocol::{
clob::{
order::{self, ConditionType, Side, TimeInForce},
Order, OrderBatch, OrderId,
},
ratelimit::{QueryCapacityByDenomRequest, QueryCapacityByDenomResponse},
subaccounts::SubaccountId,
};
use rand::{rng, Rng};
use serial_test::serial;
use std::str::FromStr;
use tokio::time::{sleep, Duration};
const ETH_USD_PAIR_ID: u32 = 1;
#[tokio::test]
async fn test_node_order_generator() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let market = env.get_market().await?;
let height = env.get_height().await?;
let account = env.account;
let price = BigDecimal::from_str("4000.0")?;
let subticks = 4_000_000_000_u64;
let quantity = BigDecimal::from_str("0.1")?;
let quantums = 100_000_000_u64;
let client_id = 123456;
let until_height = height.ahead(SHORT_TERM_ORDER_MAXIMUM_LIFETIME);
let now = Utc::now();
let until_time = now + TimeDelta::seconds(60);
let oracle_price = market
.oracle_price
.clone()
.expect("Market oracle price required for testing");
let allowed_slippage = BigDecimal::from_str("1.5")?; let one = <BigDecimal as One>::one();
let slippaged_price = oracle_price * (one - allowed_slippage.clone() / BigDecimal::from(100));
let slippaged_subticks = market
.order_params()
.quantize_price(slippaged_price)
.to_u64()
.ok_or_else(|| err!("Failed converting slippage subticks to u64"))?;
let generator = OrderBuilder::new(market, account.subaccount(0)?);
let order_ms = generator
.clone()
.market(OrderSide::Sell, quantity.clone())
.allowed_slippage(allowed_slippage.clone())
.until(until_height.clone())
.build(client_id)?;
let order_ms_r = Order {
order_id: Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: account.address().to_string(),
number: 0,
}),
client_id,
order_flags: 0_u32,
clob_pair_id: 1_u32,
}),
side: Side::Sell.into(),
quantums,
subticks: slippaged_subticks,
time_in_force: TimeInForce::Ioc.into(),
reduce_only: false,
client_metadata: DEFAULT_RUST_CLIENT_METADATA,
condition_type: ConditionType::Unspecified.into(),
conditional_order_trigger_subticks: 0u64,
good_til_oneof: Some(order::GoodTilOneof::GoodTilBlock(until_height.0)),
builder_code_parameters: None,
twap_parameters: None,
order_router_address: "".to_string(),
};
let order_mc = generator
.clone()
.stop_market(OrderSide::Sell, price.clone(), quantity.clone())
.until(until_time)
.allowed_slippage(allowed_slippage)
.execution(OrderExecution::Ioc)
.build(client_id)?;
let order_mc_r = Order {
order_id: Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: account.address().to_string(),
number: 0,
}),
client_id,
order_flags: 32_u32,
clob_pair_id: 1_u32,
}),
side: Side::Sell.into(),
quantums,
subticks: slippaged_subticks,
time_in_force: TimeInForce::Ioc.into(),
reduce_only: false,
client_metadata: DEFAULT_RUST_CLIENT_METADATA,
condition_type: ConditionType::StopLoss.into(),
conditional_order_trigger_subticks: subticks,
good_til_oneof: Some(order::GoodTilOneof::GoodTilBlockTime(
until_time.timestamp().try_into().unwrap(),
)),
builder_code_parameters: None,
twap_parameters: None,
order_router_address: "".to_string(),
};
let order_ll = generator
.clone()
.limit(OrderSide::Buy, price, quantity)
.long_term()
.until(until_time)
.build(client_id)?;
let order_ll_r = Order {
order_id: Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: account.address().to_string(),
number: 0,
}),
client_id,
order_flags: 64_u32,
clob_pair_id: 1_u32,
}),
side: Side::Buy.into(),
quantums,
subticks,
time_in_force: TimeInForce::Unspecified.into(),
reduce_only: false,
client_metadata: DEFAULT_RUST_CLIENT_METADATA,
condition_type: ConditionType::Unspecified.into(),
conditional_order_trigger_subticks: 0u64,
good_til_oneof: Some(order::GoodTilOneof::GoodTilBlockTime(
until_time.timestamp().try_into().unwrap(),
)),
builder_code_parameters: None,
twap_parameters: None,
order_router_address: "".to_string(),
};
assert_eq!(order_ms.1, order_ms_r);
assert_eq!(order_mc.1, order_mc_r);
assert_eq!(order_ll.1, order_ll_r);
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_place_order() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let market = env.get_market().await?;
let mut node = env.node;
let mut account = env.account;
let subaccount = account.subaccount(0)?;
let (_id, order) = OrderBuilder::new(market, subaccount)
.limit(OrderSide::Buy, 1, 1)
.long_term()
.until(Utc::now() + TimeDelta::seconds(60))
.build(rng().random_range(0..100_000_000))?;
let tx_res = node.place_order(&mut account, order).await;
node.query_transaction_result(tx_res).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_place_order_market_short_term() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let market = env.get_market().await?;
let height = env.get_height().await?;
let mut node = env.node;
let mut account = env.account;
let (_id, order) = OrderBuilder::new(market, account.subaccount(0)?)
.market(OrderSide::Buy, BigDecimal::from_str("0.001")?)
.price(10) .until(height.ahead(10))
.build(rng().random_range(0..100_000_000))?;
node.place_order(&mut account, order).await?;
Ok(())
}
#[tokio::test]
#[serial]
#[ignore]
async fn test_node_cancel_order() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let market = env.get_market().await?;
let mut node = env.node;
let mut account = env.account;
let subaccount = account.subaccount(0)?;
let (id, order) = OrderBuilder::new(market, subaccount)
.limit(OrderSide::Buy, 1, 1)
.until(Utc::now() + TimeDelta::seconds(60))
.long_term()
.build(rng().random_range(0..100_000_000))?;
let order_tx_hash = node.place_order(&mut account, order).await?;
node.query_transaction(&order_tx_hash).await?;
sleep(Duration::from_secs(2)).await;
let until = OrderGoodUntil::Time(Utc::now() + TimeDelta::seconds(60));
let tx_res = node.cancel_order(&mut account, id, until).await;
node.query_transaction_result(tx_res).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_deposit() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let mut account = env.account;
let sender = account.address().clone();
let recipient = account.subaccount(0)?;
let tx_res = node.deposit(&mut account, sender, recipient, 1).await;
node.query_transaction_result(tx_res).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_withdraw() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let mut account = env.account;
let sender = account.subaccount(0)?;
let recipient = account.address().clone();
let tx_res = node.withdraw(&mut account, sender, recipient, 1).await;
node.query_transaction_result(tx_res).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_transfer() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let mut account = env.account;
let sender = account.subaccount(0)?;
let recipient = account.subaccount(1)?;
let tx_res = node.transfer(&mut account, sender, recipient, 1).await;
node.query_transaction_result(tx_res).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_send_token() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let mut account = env.account;
let sender = account.address().clone();
let recipient = account.address().clone();
let tx_res = node
.send_token(&mut account, sender, recipient, Token::DydxTnt(1000.into()))
.await;
node.query_transaction_result(tx_res).await?;
Ok(())
}
#[tokio::test]
#[serial]
#[ignore]
async fn test_node_batch_cancel_orders() -> Result<(), Error> {
let mut env = TestEnv::testnet().await?;
let order_id0 = env.spawn_order().await?;
sleep(Duration::from_secs(2)).await;
let order_id1 = env.spawn_order().await?;
sleep(Duration::from_secs(2)).await;
let mut node = env.node;
let mut account = env.account;
let subaccount = account.subaccount(0)?;
let batch = OrderBatch {
clob_pair_id: ETH_USD_PAIR_ID,
client_ids: vec![order_id0.client_id, order_id1.client_id],
};
let cancels = vec![batch];
let good_til = node
.latest_block_height()
.await?
.ahead(SHORT_TERM_ORDER_MAXIMUM_LIFETIME);
let tx_res = node
.batch_cancel_orders(&mut account, subaccount, cancels, good_til)
.await;
node.query_transaction_result(tx_res).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_close_position() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let mut account = env.account;
let subaccount = account.subaccount(0)?;
let market = env
.indexer
.markets()
.get_perpetual_market(&env.ticker)
.await?;
node.close_position(
&mut account,
subaccount,
market,
None,
rng().random_range(0..100_000_000),
)
.await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_create_market_permissionless() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let mut account = env.account;
let subaccount = account.subaccount(0)?;
let ticker = Ticker::from("ETH-USD");
let tx_res = node
.create_market_permissionless(&mut account, &ticker, &subaccount)
.await;
match node.query_transaction_result(tx_res).await {
Err(e) if e.to_string().contains("Market params pair already exists") => Ok(()),
Err(e) => Err(e),
Ok(_) => Err(err!("Market creation (ETH-USD) should fail")),
}
}
#[tokio::test]
#[serial]
async fn test_node_get_withdrawal_and_transfer_gating_status() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let _get_withdrawal_and_transfer_gating_status = node
.get_withdrawal_and_transfer_gating_status(ETH_USD_PAIR_ID)
.await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_send_query() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let _tx_res: QueryCapacityByDenomResponse = node
.send_query(
QueryCapacityByDenomRequest {
denom: "adv4tnt".parse()?,
},
"/dydxprotocol.ratelimit.Query/CapacityByDenom",
)
.await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_node_get_withdrawal_capacity_by_denom() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let capacity = node.get_withdrawal_capacity_by_denom(Denom::Usdc).await?;
assert!(!capacity.is_empty());
Ok(())
}
#[tokio::test]
#[serial]
async fn test_affiliates_get_affiliate_info() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let account = env.account;
let subaccount = account.subaccount(0)?;
let _affiliate_info = node.get_affiliate_info(&subaccount.address).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_affiliates_get_affiliate_tiers() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let _affiliate_tiers = node.get_all_affiliate_tiers().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_affiliates_get_affiliate_whitelist() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let _affiliate_whitelist = node.get_affiliate_whitelist().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_affiliates_get_referred_by() -> Result<(), Error> {
let env = TestEnv::testnet().await?;
let mut node = env.node;
let _referred_by = node.get_referred_by(env.account.address().clone()).await?;
Ok(())
}