Skip to content

Commit afceb56

Browse files
committed
take deposits/withdraws after snapshot into account
1 parent 2eb92c3 commit afceb56

File tree

1 file changed

+135
-21
lines changed

1 file changed

+135
-21
lines changed

cli/src/main.rs

Lines changed: 135 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use mango::state::*;
55
use mango_common::*;
66
use serum_dex::state::OpenOrders;
77
use solana_sdk::pubkey::Pubkey;
8+
use std::collections::HashMap;
89
use std::mem::size_of;
910
use std::str::FromStr;
1011

@@ -18,6 +19,7 @@ struct Cli {
1819
#[derive(Args, Debug, Clone)]
1920
struct EquityFromSnapshotArgs {
2021
sqlite: String,
22+
late_changes: String,
2123
program: Pubkey,
2224
group: Pubkey,
2325
}
@@ -76,20 +78,6 @@ impl DataSource {
7678
anyhow::bail!("no data found for pubkey {}", address);
7779
}
7880

79-
/*
80-
fn program_account_list(&self, program: Pubkey) -> anyhow::Result<Vec<Pubkey>> {
81-
let mut stmt =
82-
self.conn.prepare_cached("SELECT DISTINCT pubkey FROM account WHERE owner = ?")?;
83-
let mut rows = stmt.query(rusqlite::params![program.as_ref()])?;
84-
let mut list = Vec::new();
85-
while let Some(row) = rows.next()? {
86-
let v: Vec<u8> = row.get(0)?;
87-
list.push(Pubkey::new(&v));
88-
}
89-
Ok(list)
90-
}
91-
*/
92-
9381
fn mango_account_list(
9482
&self,
9583
program: Pubkey,
@@ -132,18 +120,80 @@ impl DataSource {
132120
}
133121
}
134122

123+
fn late_deposits_withdrawals(filename: &str) -> anyhow::Result<Vec<(Pubkey, Pubkey, usize, i64)>> {
124+
// mango token index and decimals
125+
let tokens: HashMap<&str, (usize, i32)> = HashMap::from([
126+
("MNGO", (0, 6)),
127+
("BTC", (1, 6)),
128+
("ETH", (2, 6)),
129+
("SOL", (3, 9)),
130+
("USDT", (4, 6)),
131+
("SRM", (5, 6)),
132+
("RAY", (6, 6)),
133+
("FTT", (8, 6)),
134+
("MSOL", (10, 9)),
135+
("BNB", (11, 8)),
136+
("AVAX", (12, 8)),
137+
("GMT", (14, 9)),
138+
("USDC", (15, 6)),
139+
]);
140+
141+
let mut list = Vec::new();
142+
143+
use std::io::{BufRead, BufReader};
144+
let file = std::fs::File::open(filename)?;
145+
for line in BufReader::new(file).lines().skip(1) {
146+
if let Ok(line) = line {
147+
let fields = line.split("\t").collect::<Vec<&str>>();
148+
assert_eq!(fields.len(), 19);
149+
let account = Pubkey::from_str(fields[5]).unwrap();
150+
// skip attacker accounts
151+
if fields[5] == "4ND8FVPjUGGjx9VuGFuJefDWpg3THb58c277hbVRnjNa"
152+
|| fields[5] == "CQvKSNnYtPTZfQRQ5jkHq8q2swJyRsdQLcFcj3EmKFfX"
153+
{
154+
continue;
155+
}
156+
let owner = Pubkey::from_str(fields[6]).unwrap();
157+
let token = fields[7];
158+
let side = fields[8];
159+
let quantity = f64::from_str(&fields[9].replace(",", "")).unwrap();
160+
let token_info = tokens.get(token).unwrap();
161+
let change = (quantity
162+
* 10f64.powi(token_info.1)
163+
* (if side == "Withdraw" {
164+
-1f64
165+
} else {
166+
assert_eq!(side, "Deposit");
167+
1f64
168+
})) as i64;
169+
list.push((account, owner, token_info.0, change));
170+
}
171+
}
172+
Ok(list)
173+
}
174+
135175
struct EquityFromSnapshot {
136176
args: EquityFromSnapshotArgs,
137177
data: DataSource,
138178
group: MangoGroup,
139179
cache: MangoCache,
140180
}
141181

182+
fn cache_price(cache: &MangoCache, index: usize) -> I80F48 {
183+
if index == QUOTE_INDEX {
184+
I80F48::ONE
185+
} else {
186+
cache.price_cache[index].price
187+
}
188+
}
189+
142190
/// value of per-token equity in usd, ordered by mango group token index
143191
type AccountTokenAmounts = [i64; 16];
144192

145193
impl EquityFromSnapshot {
146194
fn run(args: EquityFromSnapshotArgs) -> anyhow::Result<()> {
195+
let late_changes = late_deposits_withdrawals(&args.late_changes)?;
196+
147197
let data = DataSource::new(args.sqlite.clone())?;
148198

149199
let group = data.load_group(args.group)?;
@@ -156,6 +206,8 @@ impl EquityFromSnapshot {
156206

157207
let mut account_equities: Vec<(Pubkey, Pubkey, AccountTokenAmounts)> =
158208
Vec::with_capacity(account_addresses.len());
209+
210+
// get the snapshot account equities
159211
for account_address in account_addresses {
160212
let equity_opt = ctx
161213
.account_equity(account_address)
@@ -167,6 +219,59 @@ impl EquityFromSnapshot {
167219
account_equities.push((account_address, owner, equity));
168220
}
169221

222+
// apply the late deposits/withdrawals
223+
for &(address, owner, token_index, change_native) in late_changes.iter() {
224+
let change_usd =
225+
(I80F48::from(change_native) * cache_price(&cache, token_index)).to_num();
226+
// slow, but just ran a handful times
227+
let account_opt = account_equities.iter_mut().find(|(a, _, _)| a == &address);
228+
if let Some((_, _, equity)) = account_opt {
229+
equity[token_index] += change_usd;
230+
} else {
231+
assert!(change_usd > 0);
232+
let mut equity = AccountTokenAmounts::default();
233+
equity[token_index] = change_usd;
234+
account_equities.push((address, owner, equity));
235+
}
236+
}
237+
238+
// Some accounts already cached out on a MNGO PERP position that started to be valuable after the
239+
// snapshot was taken, no reimbursements
240+
{
241+
let odd_accounts = [
242+
"9A6YVfa66kBEeCLtt6wyqdmjpib7UrybA5mHr3X3kyvf",
243+
"AEYWfmFVu1huajTkT3UUbvhCZx92kZXwgpWgrMtocnzv",
244+
"AZVbGBJ1DU2RnZNhZ72fcpo191DX3k1uyqDiPiaWoF1q",
245+
"C19JAyRLUjkTWmj9VpYu5eVVCbSXcbqqhyF5588ERSSf",
246+
"C9rprN4zcP7Wx87UcbLarTEAGCmGiPZp8gaFXPhY9HYm",
247+
];
248+
for odd_one in odd_accounts {
249+
let address = Pubkey::from_str(odd_one).unwrap();
250+
let (_, _, equity) =
251+
account_equities.iter_mut().find(|(a, _, _)| a == &address).unwrap();
252+
assert!(late_changes.iter().any(|(a, _, _, c)| a == &address && *c < 0));
253+
let total = equity.iter().sum::<i64>();
254+
assert!(total < 0);
255+
assert!(total > -10_000_000_000); // none of these was bigger than 10000 USD
256+
*equity = AccountTokenAmounts::default();
257+
}
258+
}
259+
260+
// Some accounts withdrew everything after the snapshot was taken. When doing that they
261+
// probably withdrew a tiny bit more than their snapshot equity due to interest.
262+
// These accounts have already cached out, no need to reimburse.
263+
for (address, _, equity) in account_equities.iter_mut() {
264+
let total = equity.iter().sum::<i64>();
265+
if total >= 0 {
266+
continue;
267+
}
268+
assert!(late_changes.iter().any(|(a, _, _, c)| a == address && *c < 0));
269+
assert!(equity.iter().sum::<i64>() < 0);
270+
// only up to -10 USD is expected, otherwise investigate manually!
271+
assert!(equity.iter().sum::<i64>() > -10_000_000);
272+
*equity = AccountTokenAmounts::default();
273+
}
274+
170275
let token_names: [&str; 16] = [
171276
"MNGO", "BTC", "ETH", "SOL", "USDT", "SRM", "RAY", "COPE", "FTT", "ADA", "MSOL", "BNB",
172277
"AVAX", "LUNA", "GMT", "USDC",
@@ -180,8 +285,6 @@ impl EquityFromSnapshot {
180285
true,
181286
];
182287

183-
//println!("{:?}", ctx.cache.price_cache.iter().map(|c| c.price).collect::<Vec<_>>());
184-
185288
// TODO: tentative numbers from "Repay bad Debt #2" proposal
186289
let available_native_amounts: [u64; 15] = [
187290
32409565000000,
@@ -201,8 +304,11 @@ impl EquityFromSnapshot {
201304
152843000000000,
202305
];
203306

307+
// Token prices at time of reimbursement
308+
// Note that user equity at snapshot time is computed from the prices from the
309+
// mango cache in the snapshot.
204310
let reimbursement_prices: [I80F48; 16] = [
205-
// TODO: bad prices
311+
// TODO: bad prices, must be updated when time comes!
206312
I80F48::from_num(0.038725),
207313
I80F48::from_num(19036.47),
208314
I80F48::from_num(1280.639999999999997),
@@ -221,6 +327,7 @@ impl EquityFromSnapshot {
221327
I80F48::ONE,
222328
];
223329

330+
// USD amounts in each token that can be used for reimbursement
224331
let available_amounts: [u64; 15] = available_native_amounts
225332
.iter()
226333
.zip(reimbursement_prices.iter())
@@ -229,6 +336,7 @@ impl EquityFromSnapshot {
229336
.try_into()
230337
.unwrap();
231338

339+
// Amounts each user should be reimbursed
232340
let mut reimburse_amounts = account_equities.clone();
233341

234342
// all the equity in unavailable tokens is just considered usdc
@@ -258,7 +366,7 @@ impl EquityFromSnapshot {
258366
// resolve user's liabilities with their assets in a way that aims to bring the
259367
// needed token amounts <= what's available
260368
let mut reimburse_amounts = account_equities.clone();
261-
for (_, _, equity) in reimburse_amounts.iter_mut() {
369+
for (adr, _, equity) in reimburse_amounts.iter_mut() {
262370
for i in 0..16 {
263371
let mut value = equity[i];
264372
// positive amounts get reimbursed
@@ -291,6 +399,8 @@ impl EquityFromSnapshot {
291399
}
292400

293401
// All tokens fine? Try reducing some random one, starting with USDC
402+
// (mSOL is last because it looks like we will have a lot of it and want
403+
// to prefer giving it out to users that had it before)
294404
for j in [15, 14, 13, 12, 11, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 10] {
295405
if equity[j] <= 0 {
296406
continue;
@@ -313,7 +423,7 @@ impl EquityFromSnapshot {
313423
// now all reimburse_amounts are >= 0
314424

315425
// Do a pass where we scale down user reimbursement token amounts and instead
316-
// reimburse with USDC if there's not enough tokens
426+
// reimburse with USDC if there's not enough tokens to give out
317427
for i in 0..15 {
318428
if reimburse_totals[i] == 0 || reimburse_totals[i] == available_amounts[i] {
319429
continue;
@@ -346,6 +456,10 @@ impl EquityFromSnapshot {
346456
}
347457

348458
// Do passes where we scale up token reimbursement amounts to try to fully utilize funds
459+
//
460+
// The idea here is that we have say 1000 SOL but only need 500 SOL to reimburse.
461+
// To leave the DAO with fewer SOL at the end we prefer to give people who already
462+
// had some SOL more of it (and compensate by giving them less of another token).
349463
for _ in 0..100 {
350464
for i in 0..15 {
351465
if reimburse_totals[i] == 0 || reimburse_totals[i] == available_amounts[i] {
@@ -387,7 +501,7 @@ impl EquityFromSnapshot {
387501
}
388502
}
389503

390-
// double check that user equity is unchanged
504+
// Double check that total user equity is unchanged
391505
for ((_, ownerl, equity), (_, ownerr, reimburse)) in
392506
account_equities.iter().zip(reimburse_amounts.iter())
393507
{
@@ -432,7 +546,7 @@ impl EquityFromSnapshot {
432546
account_address: Pubkey,
433547
) -> anyhow::Result<Option<(Pubkey, AccountTokenAmounts)>> {
434548
if account_address
435-
!= Pubkey::from_str(&"3TXDBTHFwyKywjZj1vGdRVkrF5o4YZ1vZnMf3Hb9qALz").unwrap()
549+
!= Pubkey::from_str(&"rwxRFn2S1DHkbA8wiCDxzMRMncgjUaa4LiJTagvLBr9").unwrap()
436550
{
437551
//return Ok(None);
438552
}

0 commit comments

Comments
 (0)