Skip to main content

acta_maker_sdk/chain/vault/
owner_rpc.rs

1use 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    /// Resolves the stored proposal payer and payload; priced kinds fetch the current routes.
122    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}