use alloy_primitives::{Address, U256};
use ostium_rust_sdk::{
AdvancedOrderParams, Network, OpenPositionParams, OrderExecutionType, OstiumClient,
PositionSide, UnsignedTransactionParams, UpdateTPSLParams,
};
use rust_decimal::Decimal;
use std::str::FromStr;
fn test_trader_address() -> Address {
Address::from_str("0x742d35Cc6634C0532925a3b8D53A2e1c5Bc2D8db").unwrap()
}
fn test_tx_params() -> UnsignedTransactionParams {
UnsignedTransactionParams {
from: test_trader_address(),
include_gas_estimates: true,
include_nonce: true,
}
}
#[tokio::test]
async fn test_client_creation_without_signer() {
let client = OstiumClient::new(Network::Testnet).await;
assert!(
client.is_ok(),
"Should be able to create client without signer"
);
let client = client.unwrap();
assert!(!client.has_signer(), "Client should not have a signer");
assert!(
client.signer_address().is_none(),
"Client should not have signer address"
);
}
#[tokio::test]
async fn test_unsigned_transaction_serialization() {
use ostium_rust_sdk::UnsignedTransaction;
let unsigned_tx = UnsignedTransaction {
to: test_trader_address(),
data: vec![0x11, 0x22, 0x33, 0x44],
value: U256::ZERO,
gas_limit: Some(U256::from(200000)),
gas_price: Some(U256::from(20000000000u64)), chain_id: 421614, nonce: Some(42),
};
let json = serde_json::to_string(&unsigned_tx).expect("Should serialize to JSON");
assert!(
json.contains("0x742d35cc6634c0532925a3b8d53a2e1c5bc2d8db"),
"Should contain address"
);
assert!(json.contains("421614"), "Should contain chain ID");
let deserialized: UnsignedTransaction =
serde_json::from_str(&json).expect("Should deserialize from JSON");
assert_eq!(deserialized.to, unsigned_tx.to);
assert_eq!(deserialized.data, unsigned_tx.data);
assert_eq!(deserialized.value, unsigned_tx.value);
assert_eq!(deserialized.chain_id, unsigned_tx.chain_id);
}
#[test]
fn test_unsigned_transaction_params_validation() {
let params = test_tx_params();
assert_eq!(params.from, test_trader_address());
assert!(params.include_gas_estimates);
assert!(params.include_nonce);
let json = serde_json::to_string(¶ms).expect("Should serialize to JSON");
let deserialized: UnsignedTransactionParams =
serde_json::from_str(&json).expect("Should deserialize from JSON");
assert_eq!(deserialized.from, params.from);
assert_eq!(
deserialized.include_gas_estimates,
params.include_gas_estimates
);
assert_eq!(deserialized.include_nonce, params.include_nonce);
}
#[tokio::test]
async fn test_trading_contract_unsigned_transactions() {
use alloy::providers::ProviderBuilder;
use alloy_primitives::aliases::U192;
use ostium_rust_sdk::contracts::TradingContract;
use ostium_rust_sdk::types::{OpenOrderType, Trade};
let provider = ProviderBuilder::new()
.connect("https://sepolia-rollup.arbitrum.io/rpc")
.await
.expect("Should create provider");
let contract_address = Address::from_str("0x2a9b9c988393f46a2537b0ff11e98c2c15a95afe").unwrap();
let trading_contract = TradingContract::new(contract_address, provider);
let trade = Trade {
collateral: U256::from(1000_000000u64), open_price: 0, tp: 45000_000000000000000000u128, sl: 35000_000000000000000000u128, trader: test_trader_address(),
leverage: 1000, pair_index: 0, index: 0,
buy: true, };
let tx_params = test_tx_params();
let result = trading_contract
.open_trade_unsigned(
trade.clone(),
OpenOrderType::Market,
U256::from(100),
tx_params.clone(),
)
.await;
assert!(
result.is_ok(),
"Should build unsigned transaction successfully"
);
let unsigned_tx = result.unwrap();
assert_eq!(unsigned_tx.to, contract_address);
assert!(
!unsigned_tx.data.is_empty(),
"Transaction data should not be empty"
);
assert_eq!(
unsigned_tx.value,
U256::ZERO,
"Value should be zero for contract calls"
);
assert_eq!(
unsigned_tx.chain_id, 421614,
"Should have correct chain ID for Arbitrum Sepolia"
);
assert!(
unsigned_tx.gas_limit.is_some(),
"Gas limit should be estimated"
);
assert!(
unsigned_tx.gas_price.is_some(),
"Gas price should be estimated"
);
assert!(unsigned_tx.nonce.is_some(), "Nonce should be included");
let close_result = trading_contract
.close_trade_market_unsigned(0, 0, 10000, tx_params.clone())
.await;
assert!(
close_result.is_ok(),
"Should build close trade unsigned transaction"
);
let cancel_result = trading_contract
.cancel_open_limit_order_unsigned(0, 0, tx_params.clone())
.await;
assert!(
cancel_result.is_ok(),
"Should build cancel order unsigned transaction"
);
let update_tp_result = trading_contract
.update_tp_unsigned(
0,
0,
U192::from(50000_000000000000000000u128),
tx_params.clone(),
)
.await;
assert!(
update_tp_result.is_ok(),
"Should build update TP unsigned transaction"
);
let update_sl_result = trading_contract
.update_sl_unsigned(0, 0, U192::from(30000_000000000000000000u128), tx_params)
.await;
assert!(
update_sl_result.is_ok(),
"Should build update SL unsigned transaction"
);
}
#[tokio::test]
async fn test_transaction_data_encoding() {
use alloy::providers::ProviderBuilder;
use ostium_rust_sdk::contracts::TradingContract;
use ostium_rust_sdk::types::{OpenOrderType, Trade};
let provider = ProviderBuilder::new()
.connect("https://sepolia-rollup.arbitrum.io/rpc")
.await
.expect("Should create provider");
let contract_address = Address::from_str("0x2a9b9c988393f46a2537b0ff11e98c2c15a95afe").unwrap();
let trading_contract = TradingContract::new(contract_address, provider);
let trade = Trade {
collateral: U256::from(1000_000000u64),
open_price: 0,
tp: 0,
sl: 0,
trader: test_trader_address(),
leverage: 1000,
pair_index: 0,
index: 0,
buy: true,
};
let tx_params = test_tx_params();
let result = trading_contract
.open_trade_unsigned(trade, OpenOrderType::Market, U256::from(100), tx_params)
.await;
assert!(result.is_ok());
let unsigned_tx = result.unwrap();
assert!(
unsigned_tx.data.len() >= 4,
"Transaction data should include function selector"
);
assert!(
!unsigned_tx.data.iter().all(|&b| b == 0),
"Transaction data should not be all zeros"
);
let trade2 = Trade {
collateral: U256::from(1000_000000u64),
open_price: 0,
tp: 0,
sl: 0,
trader: test_trader_address(),
leverage: 1000,
pair_index: 0,
index: 0,
buy: true,
};
let tx_params2 = UnsignedTransactionParams {
from: test_trader_address(),
include_gas_estimates: false, include_nonce: false,
};
let result2 = trading_contract
.open_trade_unsigned(trade2, OpenOrderType::Market, U256::from(100), tx_params2)
.await;
assert!(result2.is_ok());
let unsigned_tx2 = result2.unwrap();
assert_eq!(
unsigned_tx.data[0..4],
unsigned_tx2.data[0..4],
"Function selectors should match"
);
}
#[tokio::test]
async fn test_gas_estimation() {
let client = OstiumClient::new(Network::Testnet).await.unwrap();
let position_params = OpenPositionParams {
symbol: "BTC/USD".to_string(),
side: PositionSide::Long,
size: Decimal::from(1000),
leverage: Decimal::from(10),
take_profit: None,
stop_loss: None,
slippage_tolerance: Decimal::from(1) / Decimal::from(100),
};
let trader_address = test_trader_address();
let tx_params_with_gas = UnsignedTransactionParams {
from: trader_address,
include_gas_estimates: true,
include_nonce: false,
};
let tx_params_without_gas = UnsignedTransactionParams {
from: trader_address,
include_gas_estimates: false,
include_nonce: false,
};
println!("Testing gas estimation parameters...");
let _result_with_gas = client
.open_position_unsigned(position_params.clone(), trader_address, tx_params_with_gas)
.await;
let _result_without_gas = client
.open_position_unsigned(position_params, trader_address, tx_params_without_gas)
.await;
println!("Gas estimation parameter handling works correctly");
}
#[tokio::test]
async fn test_different_order_types() {
let client = OstiumClient::new(Network::Testnet).await.unwrap();
let trader_address = test_trader_address();
let tx_params = test_tx_params();
let market_order = AdvancedOrderParams {
symbol: "BTC/USD".to_string(),
side: PositionSide::Long,
size: Decimal::from(1000),
leverage: Decimal::from(5),
order_type: OrderExecutionType::Market,
price: None,
take_profit: Some(Decimal::from(45000)),
stop_loss: Some(Decimal::from(35000)),
slippage_tolerance: Decimal::from(1) / Decimal::from(100),
};
let limit_order = AdvancedOrderParams {
symbol: "BTC/USD".to_string(),
side: PositionSide::Short,
size: Decimal::from(2000),
leverage: Decimal::from(3),
order_type: OrderExecutionType::Limit,
price: Some(Decimal::from(42000)),
take_profit: Some(Decimal::from(40000)),
stop_loss: Some(Decimal::from(44000)),
slippage_tolerance: Decimal::from(1) / Decimal::from(100),
};
let stop_order = AdvancedOrderParams {
symbol: "ETH/USD".to_string(),
side: PositionSide::Long,
size: Decimal::from(1500),
leverage: Decimal::from(7),
order_type: OrderExecutionType::Stop,
price: Some(Decimal::from(2500)),
take_profit: Some(Decimal::from(2700)),
stop_loss: Some(Decimal::from(2300)),
slippage_tolerance: Decimal::from(1) / Decimal::from(100),
};
let _market_result = client
.place_advanced_order_unsigned(market_order, trader_address, tx_params.clone())
.await;
let _limit_result = client
.place_advanced_order_unsigned(limit_order, trader_address, tx_params.clone())
.await;
let _stop_result = client
.place_advanced_order_unsigned(stop_order, trader_address, tx_params)
.await;
println!("Order type handling works correctly");
}
#[tokio::test]
async fn test_batch_transaction_creation() {
let client = OstiumClient::new(Network::Testnet).await.unwrap();
let tx_params = test_tx_params();
let update_params = UpdateTPSLParams {
position_id: "0x742d35Cc6634C0532925a3b8D53A2e1c5Bc2D8db:0:0".to_string(),
take_profit: Some(Decimal::from(50000)),
stop_loss: Some(Decimal::from(30000)),
};
let result = client.update_tp_sl_unsigned(update_params, tx_params).await;
match result {
Ok(transactions) => {
assert_eq!(
transactions.len(),
2,
"Should create two transactions for TP and SL"
);
for (i, tx) in transactions.iter().enumerate() {
assert!(
!tx.data.is_empty(),
"Transaction {} data should not be empty",
i
);
assert_eq!(
tx.value,
U256::ZERO,
"Transaction {} value should be zero",
i
);
assert_eq!(
tx.chain_id, 421614,
"Transaction {} should have correct chain ID",
i
);
}
println!(
"Successfully created {} batch transactions",
transactions.len()
);
}
Err(e) => {
println!("Batch transaction creation handled error correctly: {}", e);
}
}
}
#[test]
fn test_transaction_parameter_validation() {
let valid_params = OpenPositionParams {
symbol: "BTC/USD".to_string(),
side: PositionSide::Long,
size: Decimal::from(1000),
leverage: Decimal::from(10),
take_profit: Some(Decimal::from(45000)),
stop_loss: Some(Decimal::from(35000)),
slippage_tolerance: Decimal::from(1) / Decimal::from(100),
};
let json = serde_json::to_string(&valid_params).expect("Should serialize");
let deserialized: OpenPositionParams = serde_json::from_str(&json).expect("Should deserialize");
assert_eq!(deserialized.symbol, valid_params.symbol);
assert_eq!(deserialized.side, valid_params.side);
assert_eq!(deserialized.size, valid_params.size);
assert_eq!(deserialized.leverage, valid_params.leverage);
assert_eq!(deserialized.take_profit, valid_params.take_profit);
assert_eq!(deserialized.stop_loss, valid_params.stop_loss);
assert_eq!(
deserialized.slippage_tolerance,
valid_params.slippage_tolerance
);
}