acta_maker_sdk/chain/vault/
owner_rpc.rs1use super::{PriceRoute, VaultAccount, VaultChainError, VaultGovernanceUpdateKind, VaultManager};
2use crate::chain::{ChainClient, ChainError};
3use solana_sdk::{
4 instruction::{AccountMeta, Instruction},
5 pubkey::Pubkey,
6};
7
8impl ChainClient {
9 pub async fn vault_manager(
10 &self,
11 vault: Pubkey,
12 actor: Pubkey,
13 ) -> Result<VaultManager, ChainError> {
14 let state =
15 VaultAccount::from_account(&self.fetch_account(&vault).await?, &self.program_id())?;
16 let manager = VaultManager::from_vault(self.program_id(), actor, &state)?;
17 if manager.addresses.vault != vault {
18 return Err(ChainError::Identity("vault PDA"));
19 }
20 Ok(manager)
21 }
22}
23
24impl VaultManager {
25 async fn current_owner_state(&self, chain: &ChainClient) -> Result<VaultAccount, ChainError> {
26 if chain.program_id() != self.args.program_id {
27 return Err(ChainError::Identity("vault program"));
28 }
29 let state = VaultAccount::from_account(
30 &chain.fetch_account(&self.addresses.vault).await?,
31 &chain.program_id(),
32 )?;
33 self.ensure_current(&state)?;
34 if state.primary_delegate != self.args.delegate_authority {
35 return Err(ChainError::Identity("vault delegate"));
36 }
37 Ok(state)
38 }
39
40 pub async fn fetch_price_section(
41 &self,
42 chain: &ChainClient,
43 shard: u16,
44 ) -> Result<Vec<AccountMeta>, ChainError> {
45 self.current_owner_state(chain).await?;
46 self.load_price_section(chain, shard).await
47 }
48
49 async fn load_price_section(
50 &self,
51 chain: &ChainClient,
52 shard: u16,
53 ) -> Result<Vec<AccountMeta>, ChainError> {
54 let main = PriceRoute::from_account(
55 &chain
56 .fetch_account(&self.addresses.main_price_route)
57 .await?,
58 &chain.program_id(),
59 )?;
60 let second = PriceRoute::from_account(
61 &chain
62 .fetch_account(&self.addresses.second_price_route)
63 .await?,
64 &chain.program_id(),
65 )?;
66 Ok(self.price_section(&main, &second, shard)?)
67 }
68
69 pub async fn top_up_manager_stake_from_chain(
70 &self,
71 chain: &ChainClient,
72 amount: u64,
73 shard: u16,
74 ) -> Result<Instruction, ChainError> {
75 let state = self.current_owner_state(chain).await?;
76 self.require_governance(&state)?;
77 require_unstaged(&state)?;
78 Ok(self.top_up_manager_stake(amount, &self.load_price_section(chain, shard).await?)?)
79 }
80
81 pub async fn finalize_shutdown_from_chain(
82 &self,
83 chain: &ChainClient,
84 shard: u16,
85 ) -> Result<Instruction, ChainError> {
86 let state = self.current_owner_state(chain).await?;
87 require_unstaged(&state)?;
88 let prices = self.prices_for_second_balance(chain, shard).await?;
89 Ok(self.finalize_shutdown(&prices))
90 }
91
92 pub async fn unlock_manager_stake_from_chain(
93 &self,
94 chain: &ChainClient,
95 shares: u64,
96 ) -> Result<Instruction, ChainError> {
97 let state = self.current_owner_state(chain).await?;
98 self.require_governance(&state)?;
99 Ok(self.unlock_manager_stake(state.fee_recipient, shares)?)
100 }
101
102 pub async fn apply_fee_recipient_update_from_chain(
103 &self,
104 chain: &ChainClient,
105 recipient: Pubkey,
106 ) -> Result<Instruction, ChainError> {
107 let state = self.current_owner_state(chain).await?;
108 self.require_governance(&state)?;
109 Ok(self.apply_fee_recipient_update(state.fee_recipient, recipient)?)
110 }
111
112 pub async fn revoke_governance_nomination_from_chain(
113 &self,
114 chain: &ChainClient,
115 ) -> Result<Instruction, ChainError> {
116 let state = self.current_owner_state(chain).await?;
117 self.require_governance(&state)?;
118 Ok(self.revoke_governance_nomination(state.pending_governance_authority)?)
119 }
120
121 pub async fn apply_update_from_chain(
123 &self,
124 chain: &ChainClient,
125 kind: VaultGovernanceUpdateKind,
126 shard: u16,
127 ) -> Result<Instruction, ChainError> {
128 let state = self.current_owner_state(chain).await?;
129 if matches!(
130 kind,
131 VaultGovernanceUpdateKind::Fees
132 | VaultGovernanceUpdateKind::HwmReset
133 | VaultGovernanceUpdateKind::SecondLeg
134 ) {
135 require_unstaged(&state)?;
136 }
137 let (payer, payload) = self.fetch_proposal(chain, kind).await?;
138 if kind == VaultGovernanceUpdateKind::SecondLeg {
139 let prices = self.prices_for_second_balance(chain, shard).await?;
140 return Ok(self.apply_second_leg(
141 payer,
142 Pubkey::new_from_array(payload),
143 Some(&prices),
144 )?);
145 }
146 let prices = if matches!(
147 kind,
148 VaultGovernanceUpdateKind::Fees | VaultGovernanceUpdateKind::HwmReset
149 ) {
150 Some(self.load_price_section(chain, shard).await?)
151 } else {
152 None
153 };
154 Ok(self.apply_update(kind, payer, prices.as_deref())?)
155 }
156
157 pub async fn cancel_update_from_chain(
158 &self,
159 chain: &ChainClient,
160 kind: VaultGovernanceUpdateKind,
161 ) -> Result<Instruction, ChainError> {
162 if kind == VaultGovernanceUpdateKind::HwmReset {
163 return Err(VaultChainError::ColdAuthorityRequired.into());
164 }
165 self.current_owner_state(chain).await?;
166 let (payer, _) = self.fetch_proposal(chain, kind).await?;
167 Ok(self.cancel_update(kind, payer))
168 }
169
170 pub async fn unlock_manager_stake_sequence_from_chain(
171 &self,
172 chain: &ChainClient,
173 shares: u64,
174 ) -> Result<Vec<Instruction>, ChainError> {
175 let state = self.current_owner_state(chain).await?;
176 self.require_governance(&state)?;
177 Ok(self.unlock_manager_stake_sequence(state.fee_recipient, shares)?)
178 }
179
180 pub async fn start_shutdown_from_chain(
181 &self,
182 chain: &ChainClient,
183 ) -> Result<Instruction, ChainError> {
184 let state = self.current_owner_state(chain).await?;
185 self.require_governance(&state)?;
186 require_unstaged(&state)?;
187 Ok(self.start_shutdown())
188 }
189
190 pub async fn close_vault_from_chain(
191 &self,
192 chain: &ChainClient,
193 ) -> Result<Instruction, ChainError> {
194 let state = self.current_owner_state(chain).await?;
195 self.require_governance(&state)?;
196 require_unstaged(&state)?;
197 Ok(self.close_vault())
198 }
199
200 fn require_governance(&self, state: &VaultAccount) -> Result<(), VaultChainError> {
201 if self.args.actor != state.governance_authority {
202 return Err(VaultChainError::GovernanceAuthorityRequired);
203 }
204 Ok(())
205 }
206
207 async fn prices_for_second_balance(
208 &self,
209 chain: &ChainClient,
210 shard: u16,
211 ) -> Result<Vec<AccountMeta>, ChainError> {
212 let account = chain
213 .fetch_account(&self.addresses.vault_second_ata)
214 .await?;
215 let data = &account.data;
216 if account.owner != self.args.second_token_program
217 || data.len() < 165
218 || data[..32] != self.args.second_mint.to_bytes()
219 || data[32..64] != self.addresses.vault.to_bytes()
220 || data[108] != 1
221 {
222 return Err(ChainError::InvalidAccountData);
223 }
224 let amount = u64::from_le_bytes(
225 data[64..72]
226 .try_into()
227 .map_err(|_| ChainError::InvalidAccountData)?,
228 );
229 let amount = match &data[109..113] {
230 [0, 0, 0, 0] => amount,
231 [1, 0, 0, 0] => {
232 let reserve = u64::from_le_bytes(
233 data[113..121]
234 .try_into()
235 .map_err(|_| ChainError::InvalidAccountData)?,
236 );
237 account
238 .lamports
239 .checked_sub(reserve)
240 .filter(|synced| *synced >= amount)
241 .ok_or(ChainError::InvalidAccountData)?
242 }
243 _ => return Err(ChainError::InvalidAccountData),
244 };
245 if amount == 0 {
246 Ok(Vec::new())
247 } else {
248 self.load_price_section(chain, shard).await
249 }
250 }
251
252 async fn fetch_proposal(
253 &self,
254 chain: &ChainClient,
255 kind: VaultGovernanceUpdateKind,
256 ) -> Result<(Pubkey, [u8; 32]), ChainError> {
257 let (address, bump) = super::pda::pending_vault_update(
258 &self.args.program_id,
259 &self.addresses.vault,
260 kind as u8,
261 );
262 let account = chain.fetch_account(&address).await?;
263 if account.owner != chain.program_id() {
264 return Err(ChainError::AccountOwnerMismatch {
265 expected: chain.program_id(),
266 actual: account.owner,
267 });
268 }
269 let data = &account.data;
270 if data.len() != 368
271 || data[0] != 14
272 || data[1] != 1
273 || data[2] != bump
274 || data[3] != kind as u8
275 || data[8..40] != self.addresses.vault.to_bytes()
276 || data[40..72] == [0; 32]
277 {
278 return Err(ChainError::InvalidAccountData);
279 }
280 let payer = data[40..72]
281 .try_into()
282 .map_err(|_| ChainError::InvalidAccountData)?;
283 let payload = data[80..112]
284 .try_into()
285 .map_err(|_| ChainError::InvalidAccountData)?;
286 Ok((Pubkey::new_from_array(payer), payload))
287 }
288}
289
290fn require_unstaged(state: &VaultAccount) -> Result<(), VaultChainError> {
291 if state.staging.open.is_some() {
292 return Err(VaultChainError::Staged);
293 }
294 Ok(())
295}