1use alloy::primitives::{Address, U256};
2use fastnum::{D256, UD128};
3
4use super::*;
5use crate::{
6 abi::dex::Exchange::{AccountInfo, PositionBitMap},
7 types,
8};
9
10#[derive(Clone, derive_more::Debug)]
12pub struct Account {
13 instant: types::StateInstant,
14 id: types::AccountId,
15 address: Address,
16 #[debug("{balance}")]
17 balance: UD128, #[debug("{locked_balance}")]
19 locked_balance: UD128, frozen: bool,
21 fee_tier: Option<types::FeeTier>,
22 positions: HashMap<types::PerpetualId, Position>,
23}
24
25impl Account {
26 pub(crate) fn new(
27 instant: types::StateInstant,
28 id: types::AccountId,
29 info: &AccountInfo,
30 fee_tier: Option<types::FeeTier>,
31 positions: HashMap<types::PerpetualId, Position>,
32 collateral_converter: num::Converter,
33 ) -> Self {
34 Self {
35 instant,
36 id,
37 address: info.accountAddr,
38 balance: collateral_converter.from_unsigned(info.balanceCNS),
39 locked_balance: collateral_converter.from_unsigned(info.lockedBalanceCNS),
40 frozen: info.frozen != 0,
41 fee_tier,
42 positions,
43 }
44 }
45
46 pub(crate) fn created(
50 instant: types::StateInstant,
51 id: types::AccountId,
52 address: Address,
53 ) -> Self {
54 Self {
55 instant,
56 id,
57 address,
58 balance: UD128::ZERO,
59 locked_balance: UD128::ZERO,
60 frozen: false,
61 fee_tier: Some(0),
62 positions: HashMap::new(),
63 }
64 }
65
66 pub(crate) fn untracked(instant: types::StateInstant, id: types::AccountId) -> Self {
70 Self {
71 instant,
72 id,
73 address: Address::ZERO,
74 balance: UD128::ZERO,
75 locked_balance: UD128::ZERO,
76 frozen: false,
77 fee_tier: None,
78 positions: HashMap::new(),
79 }
80 }
81
82 pub(crate) fn from_position(instant: types::StateInstant, position: Position) -> Self {
83 let account_id = position.account_id();
84 let mut positions = HashMap::new();
85 positions.insert(position.perpetual_id(), position);
86 Self {
87 instant,
88 id: account_id,
89 address: Address::ZERO,
90 balance: UD128::ZERO,
91 locked_balance: UD128::ZERO,
92 frozen: false,
93 fee_tier: None,
96 positions,
97 }
98 }
99
100 pub fn instant(&self) -> types::StateInstant { self.instant }
102
103 pub fn id(&self) -> types::AccountId { self.id }
105
106 pub fn address(&self) -> Address { self.address }
108
109 pub fn balance(&self) -> UD128 { self.balance }
112
113 pub fn locked_balance(&self) -> UD128 { self.locked_balance }
118
119 pub fn available_balance(&self) -> UD128 {
121 if self.locked_balance > self.balance {
122 return UD128::ZERO;
124 }
125 self.balance - self.locked_balance
126 }
127
128 pub fn unrealized_pnl(&self) -> D256 { self.positions.values().map(|p| p.pnl()).sum() }
130
131 pub fn frozen(&self) -> bool { self.frozen }
133
134 pub fn fee_tier(&self) -> Option<types::FeeTier> { self.fee_tier }
143
144 pub fn positions(&self) -> &HashMap<types::PerpetualId, position::Position> { &self.positions }
146
147 pub(crate) fn update_frozen(&mut self, instant: types::StateInstant, frozen: bool) {
148 self.frozen = frozen;
149 self.instant = instant;
150 }
151
152 pub(crate) fn update_fee_tier(&mut self, instant: types::StateInstant, tier: types::FeeTier) {
153 self.fee_tier = Some(tier);
154 self.instant = instant;
155 }
156
157 pub(crate) fn update_balance(&mut self, instant: types::StateInstant, balance: UD128) {
158 self.balance = balance;
159 self.instant = instant;
160 }
161
162 pub(crate) fn update_locked_balance(
163 &mut self,
164 instant: types::StateInstant,
165 locked_balance: UD128,
166 ) {
167 self.locked_balance = locked_balance;
168 self.instant = instant;
169 }
170
171 pub(crate) fn positions_mut(&mut self) -> &mut HashMap<types::PerpetualId, position::Position> {
172 &mut self.positions
173 }
174}
175
176#[cfg(feature = "display")]
177impl std::fmt::Display for Account {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 use colored::Colorize;
180 use tabled::{Table, settings::Style};
181
182 if !self.address.is_zero() {
183 let pnl = self.unrealized_pnl();
185 writeln!(
186 f,
187 "{} ({}) {}\n Balance: {} | Available: {} | Locked: {} | Unrealized PnL: {} | \
188 Fee Tier: {}",
189 format!("Account #{}", self.id).blue(),
190 self.address,
191 if self.frozen { "FROZEN ".bright_red() } else { Default::default() },
192 self.balance,
193 self.available_balance().to_string().green(),
194 self.locked_balance,
195 if pnl.is_negative() { pnl.to_string().red() } else { pnl.to_string().green() },
196 self.fee_tier
197 .map(|tier| tier.to_string())
198 .unwrap_or("?".to_string()),
199 )?;
200 } else {
201 writeln!(f, "{}", format!("Account #{}", self.id).blue())?;
203 }
204
205 if f.alternate() {
207 let mut positions: Vec<_> = self.positions().values().collect();
208 positions.sort_by_key(|p| p.perpetual_id());
209 let mut positions_table = Table::new(positions);
210 positions_table.with(Style::sharp());
211 positions_table.fmt(f)
212 } else {
213 Ok(())
214 }
215 }
216}
217
218pub(crate) fn perpetuals_with_position(bitmap: &PositionBitMap) -> Vec<types::PerpetualId> {
220 let banks = vec![
221 (0, (0..U256::BITS - 3), bitmap.bank1, bitmap.bank1.count_ones()),
222 (253, (0..U256::BITS), bitmap.bank2, bitmap.bank2.count_ones()),
223 (509, (0..U256::BITS), bitmap.bank3, bitmap.bank3.count_ones()),
224 (765, (0..U256::BITS), bitmap.bank4, bitmap.bank4.count_ones()),
225 ];
226 banks
227 .into_iter()
228 .filter(|(_, _, _, count)| *count > 0)
229 .flat_map(|(offs, range, bank, _)| {
230 range.filter_map(move |i| bank.bit(i).then_some((offs + i) as types::PerpetualId))
231 })
232 .collect()
233}