Skip to content

Commit 3fdcd88

Browse files
Vault: Fix issue with stale update vaults (#163)
Forces the vault to update from the previous state, given missed update epochs or partial updates that have not called close_vault_update_state_tracker
1 parent e66e86d commit 3fdcd88

File tree

9 files changed

+368
-23
lines changed

9 files changed

+368
-23
lines changed

clients/js/vault_client/accounts/vault.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export type Vault = {
8282
programFeeBps: number;
8383
bump: number;
8484
isPaused: number;
85+
lastStartStateUpdateSlot: bigint;
8586
reserved: Array<number>;
8687
};
8788

@@ -122,6 +123,7 @@ export type VaultArgs = {
122123
programFeeBps: number;
123124
bump: number;
124125
isPaused: number;
126+
lastStartStateUpdateSlot: number | bigint;
125127
reserved: Array<number>;
126128
};
127129

@@ -163,7 +165,8 @@ export function getVaultEncoder(): Encoder<VaultArgs> {
163165
['programFeeBps', getU16Encoder()],
164166
['bump', getU8Encoder()],
165167
['isPaused', getBoolEncoder()],
166-
['reserved', getArrayEncoder(getU8Encoder(), { size: 259 })],
168+
['lastStartStateUpdateSlot', getU64Encoder()],
169+
['reserved', getArrayEncoder(getU8Encoder(), { size: 251 })],
167170
]);
168171
}
169172

@@ -205,7 +208,8 @@ export function getVaultDecoder(): Decoder<Vault> {
205208
['programFeeBps', getU16Decoder()],
206209
['bump', getU8Decoder()],
207210
['isPaused', getBoolDecoder()],
208-
['reserved', getArrayDecoder(getU8Decoder(), { size: 259 })],
211+
['lastStartStateUpdateSlot', getU64Decoder()],
212+
['reserved', getArrayDecoder(getU8Decoder(), { size: 251 })],
209213
]);
210214
}
211215

clients/rust/vault_client/src/generated/accounts/vault.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ pub struct Vault {
104104
pub program_fee_bps: u16,
105105
pub bump: u8,
106106
pub is_paused: bool,
107+
pub last_start_state_update_slot: u64,
107108
#[cfg_attr(feature = "serde", serde(with = "serde_with::As::<serde_with::Bytes>"))]
108-
pub reserved: [u8; 259],
109+
pub reserved: [u8; 251],
109110
}
110111

111112
impl Vault {

idl/jito_vault.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1652,12 +1652,18 @@
16521652
"defined": "PodBool"
16531653
}
16541654
},
1655+
{
1656+
"name": "lastStartStateUpdateSlot",
1657+
"type": {
1658+
"defined": "PodU64"
1659+
}
1660+
},
16551661
{
16561662
"name": "reserved",
16571663
"type": {
16581664
"array": [
16591665
"u8",
1660-
259
1666+
251
16611667
]
16621668
}
16631669
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use jito_vault_core::{
4+
config::Config, vault_operator_delegation::VaultOperatorDelegation,
5+
vault_update_state_tracker::VaultUpdateStateTracker,
6+
};
7+
use solana_sdk::signature::{Keypair, Signer};
8+
9+
use crate::fixtures::{
10+
fixture::{ConfiguredVault, TestBuilder},
11+
vault_client::VaultStakerWithdrawalTicketRoot,
12+
};
13+
14+
#[tokio::test]
15+
async fn test_dos_vault_if_state_tracker_not_closed_when_additional_assets_need_unstaking_is_zero(
16+
) {
17+
let mut fixture = TestBuilder::new().await;
18+
19+
let deposit_fee_bps = 0;
20+
let withdrawal_fee_bps = 0;
21+
let reward_fee_bps = 0;
22+
let num_operators = 1;
23+
let slasher_amounts = vec![];
24+
25+
let ConfiguredVault {
26+
mut vault_program_client,
27+
vault_root,
28+
operator_roots,
29+
..
30+
} = fixture
31+
.setup_vault_with_ncn_and_operators(
32+
deposit_fee_bps,
33+
withdrawal_fee_bps,
34+
reward_fee_bps,
35+
num_operators,
36+
&slasher_amounts,
37+
)
38+
.await
39+
.unwrap();
40+
41+
let depositor = Keypair::new();
42+
vault_program_client
43+
.configure_depositor(&vault_root, &depositor.pubkey(), 100_000)
44+
.await
45+
.unwrap();
46+
vault_program_client
47+
.do_mint_to(&vault_root, &depositor, 100_000, 100_000)
48+
.await
49+
.unwrap();
50+
51+
vault_program_client
52+
.do_add_delegation(&vault_root, &operator_roots[0].operator_pubkey, 100_000)
53+
.await
54+
.unwrap();
55+
56+
let config = vault_program_client
57+
.get_config(&Config::find_program_address(&jito_vault_program::id()).0)
58+
.await
59+
.unwrap();
60+
61+
let VaultStakerWithdrawalTicketRoot { base: _ } = vault_program_client
62+
.do_enqueue_withdrawal(&vault_root, &depositor, 10_000)
63+
.await
64+
.unwrap();
65+
let vault = vault_program_client
66+
.get_vault(&vault_root.vault_pubkey)
67+
.await
68+
.unwrap();
69+
assert_eq!(vault.vrt_enqueued_for_cooldown_amount(), 10_000);
70+
assert_eq!(vault.vrt_cooling_down_amount(), 0);
71+
assert_eq!(vault.vrt_ready_to_claim_amount(), 0);
72+
73+
fixture
74+
.warp_slot_incremental(config.epoch_length())
75+
.await
76+
.unwrap();
77+
78+
let operator_pubkeys: Vec<_> = operator_roots
79+
.iter()
80+
.map(|root| root.operator_pubkey)
81+
.collect();
82+
83+
//start of full vault update
84+
let slot = fixture.get_current_slot().await.unwrap();
85+
86+
let ncn_epoch = slot / config.epoch_length();
87+
88+
let vault_update_state_tracker = VaultUpdateStateTracker::find_program_address(
89+
&jito_vault_program::id(),
90+
&vault_root.vault_pubkey,
91+
ncn_epoch,
92+
)
93+
.0;
94+
//initialize vault_update_state_tracker
95+
vault_program_client
96+
.initialize_vault_update_state_tracker(
97+
&vault_root.vault_pubkey,
98+
&vault_update_state_tracker,
99+
)
100+
.await
101+
.unwrap();
102+
//crank update all operators so addoitional_assets_need_unstaking=0
103+
for i in 0..operator_pubkeys.len() {
104+
let operator_index = (i + (ncn_epoch as usize)) % operator_pubkeys.len();
105+
let operator = &operator_pubkeys[operator_index];
106+
vault_program_client
107+
.crank_vault_update_state_tracker(
108+
&vault_root.vault_pubkey,
109+
operator,
110+
&VaultOperatorDelegation::find_program_address(
111+
&jito_vault_program::id(),
112+
&vault_root.vault_pubkey,
113+
operator,
114+
)
115+
.0,
116+
&vault_update_state_tracker,
117+
)
118+
.await
119+
.unwrap();
120+
}
121+
//fast forward to next epoch without closing update_state_tracker even though all operators have been updated
122+
fixture
123+
.warp_slot_incremental(config.epoch_length())
124+
.await
125+
.unwrap();
126+
//close the update state tracker(not necessary though)
127+
vault_program_client
128+
.close_vault_update_state_tracker(
129+
&vault_root.vault_pubkey,
130+
&vault_update_state_tracker,
131+
slot / config.epoch_length(),
132+
)
133+
.await
134+
.unwrap();
135+
136+
//end of full vault update
137+
138+
//initialize another update state tracker
139+
let slot = fixture.get_current_slot().await.unwrap();
140+
141+
let ncn_epoch = slot / config.epoch_length();
142+
143+
let vault_update_state_tracker = VaultUpdateStateTracker::find_program_address(
144+
&jito_vault_program::id(),
145+
&vault_root.vault_pubkey,
146+
ncn_epoch,
147+
)
148+
.0;
149+
//new epoch: initialize new vault update state tracker
150+
vault_program_client
151+
.initialize_vault_update_state_tracker(
152+
&vault_root.vault_pubkey,
153+
&vault_update_state_tracker,
154+
)
155+
.await
156+
.unwrap();
157+
//crank update all operators
158+
for i in 0..operator_pubkeys.len() {
159+
let operator_index = (i + (ncn_epoch as usize)) % operator_pubkeys.len();
160+
let operator = &operator_pubkeys[operator_index];
161+
vault_program_client
162+
.crank_vault_update_state_tracker(
163+
&vault_root.vault_pubkey,
164+
operator,
165+
&VaultOperatorDelegation::find_program_address(
166+
&jito_vault_program::id(),
167+
&vault_root.vault_pubkey,
168+
operator,
169+
)
170+
.0,
171+
&vault_update_state_tracker,
172+
)
173+
.await
174+
.unwrap();
175+
}
176+
//closing vault_update_state_tracker fails cos additional_assets_need_unstaking>0 as there were no more delegations to decrement from the operators
177+
let result = vault_program_client
178+
.close_vault_update_state_tracker(
179+
&vault_root.vault_pubkey,
180+
&vault_update_state_tracker,
181+
slot / config.epoch_length(),
182+
)
183+
.await;
184+
185+
let vault = vault_program_client
186+
.get_vault(&vault_root.vault_pubkey)
187+
.await
188+
.unwrap();
189+
190+
assert_eq!(vault.additional_assets_need_unstaking(), 0);
191+
192+
assert!(result.is_ok());
193+
194+
// Previous result
195+
// //Error cos additional_assets_need_unstaking>0
196+
// assert_vault_error(
197+
// result,
198+
// VaultError::NonZeroAdditionalAssetsNeededForWithdrawalAtEndOfUpdate,
199+
// );
200+
// //Now that vault_update_state_tracker can't be closed, all other operations are impossible:withdrawals, delegations, cooldowns...
201+
202+
// end of block
203+
}
204+
}

integration_tests/tests/vault/initialize_vault_update_state_tracker.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ mod tests {
293293
.await
294294
.unwrap();
295295

296+
// Do full update
297+
vault_program_client
298+
.do_full_vault_update(&vault_root.vault_pubkey, &[operator_root.operator_pubkey])
299+
.await
300+
.unwrap();
301+
296302
// Set fees
297303
let new_withdrawal_fee_bps = 10;
298304
let new_program_fee_bps = 11;
@@ -427,12 +433,11 @@ mod tests {
427433
.unwrap();
428434

429435
fixture
430-
.warp_slot_incremental(2 * config.epoch_length())
436+
.warp_slot_incremental(1 * config.epoch_length())
431437
.await
432438
.unwrap();
433439

434440
// enqueued for cool down assets are now cooling down
435-
436441
let slot = fixture.get_current_slot().await.unwrap();
437442
let ncn_epoch = slot / config.epoch_length();
438443

@@ -442,6 +447,7 @@ mod tests {
442447
ncn_epoch,
443448
)
444449
.0;
450+
445451
vault_program_client
446452
.initialize_vault_update_state_tracker(
447453
&vault_root.vault_pubkey,
@@ -460,6 +466,13 @@ mod tests {
460466
75_000 - Vault::DEFAULT_INITIALIZATION_TOKEN_AMOUNT
461467
);
462468

469+
// skip cranking operator 0, advance to next epoch
470+
471+
fixture
472+
.warp_slot_incremental(config.epoch_length())
473+
.await
474+
.unwrap();
475+
463476
// Update fees
464477
let new_withdrawal_fee_bps = 10;
465478
let new_program_fee_bps = 11;
@@ -479,13 +492,6 @@ mod tests {
479492
.await
480493
.unwrap();
481494

482-
// skip cranking operator 0, advance to next epoch
483-
484-
fixture
485-
.warp_slot_incremental(config.epoch_length())
486-
.await
487-
.unwrap();
488-
489495
let slot = fixture.get_current_slot().await.unwrap();
490496
let ncn_epoch = slot / config.epoch_length();
491497
// no assets cooled down, additional_assets_need_unstaking = 75_000

integration_tests/tests/vault/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod add_delegation;
22
mod burn_withdrawal_ticket;
33
mod close_update_state_tracker;
4+
mod close_vault_update_state_tracker;
45
mod cooldown_delegation;
56
mod cooldown_vault_ncn_ticket;
67
mod crank_vault_update_state_tracker;

0 commit comments

Comments
 (0)