Skip to main content

dlp_api/
requires.rs

1use pinocchio::{
2    address::{address_eq, Address},
3    error::ProgramError,
4    AccountView,
5};
6use pinocchio_log::log;
7
8use crate::{
9    error::DlpError,
10    pda::{
11        self, program_config_from_program_id,
12        validator_fees_vault_pda_from_validator,
13    },
14};
15
16// require true
17#[macro_export]
18macro_rules! require {
19    ($cond:expr, $error:expr) => {{
20        if !$cond {
21            let expr = stringify!($cond);
22            pinocchio_log::log!("require!({}) failed.", expr);
23            return Err($error.into());
24        }
25    }};
26}
27
28// require (info.is_signer())
29#[macro_export]
30macro_rules! require_signer {
31    ($info: expr) => {{
32        if !$info.is_signer() {
33            log!("require_signer!({}): ", stringify!($info));
34            $info.address().log();
35            return Err(ProgramError::MissingRequiredSignature);
36        }
37    }};
38}
39
40// require key1 == key2
41#[macro_export]
42macro_rules! require_eq_keys {
43    ( $key1:expr, $key2:expr, $error:expr) => {{
44        if !pinocchio::address::address_eq($key1, $key2) {
45            pinocchio_log::log!(
46                "require_eq_keys!({}, {}) failed: ",
47                stringify!($key1),
48                stringify!($key2)
49            );
50            $key1.log();
51            $key2.log();
52            return Err($error.into());
53        }
54    }};
55}
56
57// require a == b
58#[macro_export]
59macro_rules! require_eq {
60    ( $val1:expr, $val2:expr, $error:expr) => {{
61        if !($val1 == $val2) {
62            pinocchio_log::log!(
63                "require_eq!({}, {}) failed: {} == {}",
64                stringify!($val1),
65                stringify!($val2),
66                $val1,
67                $val2
68            );
69            return Err($error.into());
70        }
71    }};
72}
73
74// require a <= b
75#[macro_export]
76macro_rules! require_le {
77    ( $val1:expr, $val2:expr, $error:expr) => {{
78        if !($val1 <= $val2) {
79            pinocchio_log::log!(
80                "require_le!({}, {}) failed: {} <= {}",
81                stringify!($val1),
82                stringify!($val2),
83                $val1,
84                $val2
85            );
86            return Err($error.into());
87        }
88    }};
89}
90
91// require a < b
92#[macro_export]
93macro_rules! require_lt {
94    ( $val1:expr, $val2:expr, $error:expr) => {{
95        if !($val1 < $val2) {
96            pinocchio_log::log!(
97                "require_lt!({}, {}) failed: {} < {}",
98                stringify!($val1),
99                stringify!($val2),
100                $val1,
101                $val2
102            );
103            return Err($error.into());
104        }
105    }};
106}
107
108// require a >= b
109#[macro_export]
110macro_rules! require_ge {
111    ( $val1:expr, $val2:expr, $error:expr) => {{
112        if !($val1 >= $val2) {
113            pinocchio_log::log!(
114                "require_ge!({}, {}) failed: {} >= {}",
115                stringify!($val1),
116                stringify!($val2),
117                $val1,
118                $val2
119            );
120            return Err($error.into());
121        }
122    }};
123}
124
125// require a > b
126#[macro_export]
127macro_rules! require_gt {
128    ( $val1:expr, $val2:expr, $error:expr) => {{
129        if !($val1 > $val2) {
130            pinocchio_log::log!(
131                "require_gt!({}, {}) failed: {} > {}",
132                stringify!($val1),
133                stringify!($val2),
134                $val1,
135                $val2
136            );
137            return Err($error.into());
138        }
139    }};
140}
141
142#[macro_export]
143macro_rules! require_n_accounts {
144    ( $accounts:expr, $n:literal) => {{
145        match $accounts.len().cmp(&$n) {
146            core::cmp::Ordering::Less => {
147                pinocchio_log::log!(
148                    "Need {} accounts, but got less ({}) accounts",
149                    $n,
150                    $accounts.len()
151                );
152                return Err(
153                    pinocchio::error::ProgramError::NotEnoughAccountKeys,
154                );
155            }
156            core::cmp::Ordering::Equal => {
157                TryInto::<&[_; $n]>::try_into($accounts)
158                    .map_err(|_| $crate::error::DlpError::InfallibleError)?
159            }
160            core::cmp::Ordering::Greater => {
161                pinocchio_log::log!(
162                    "Need {} accounts, but got more ({}) accounts",
163                    $n,
164                    $accounts.len()
165                );
166                return Err($crate::error::DlpError::TooManyAccountKeys.into());
167            }
168        }
169    }};
170}
171
172#[macro_export]
173macro_rules! require_n_accounts_with_optionals {
174    ( $accounts:expr, $n:literal) => {{
175        match $accounts.len().cmp(&$n) {
176            core::cmp::Ordering::Less => {
177                pinocchio_log::log!(
178                    "Need {} accounts, but got less ({}) accounts",
179                    $n,
180                    $accounts.len()
181                );
182                return Err(
183                    pinocchio::error::ProgramError::NotEnoughAccountKeys,
184                );
185            }
186            _ => {
187                let (exact, optionals) = $accounts.split_at($n);
188
189                (
190                    TryInto::<&[_; $n]>::try_into(exact).map_err(|_| {
191                        $crate::error::DlpError::InfallibleError
192                    })?,
193                    optionals,
194                )
195            }
196        }
197    }};
198}
199
200#[macro_export]
201macro_rules! require_some {
202    ($option:expr, $error:expr) => {{
203        match $option {
204            Some(val) => val,
205            None => return Err($error.into()),
206        }
207    }};
208}
209
210///
211/// require_owned_by(
212///     info: &AccountView,
213///     owner: &Address
214/// ) -> Result<(), ProgramError>
215///
216#[macro_export]
217macro_rules! require_owned_by {
218    ($info: expr, $owner: expr) => {{
219        if !address_eq(unsafe { $info.owner() }, $owner) {
220            pinocchio_log::log!(
221                "require_owned_by!({}, {})",
222                stringify!($info),
223                stringify!($owner)
224            );
225            $info.address().log();
226            $owner.log();
227            return Err(ProgramError::InvalidAccountOwner);
228        }
229    }};
230}
231
232///
233/// require_initialized_pda(
234///     info: &AccountView,
235///     seeds: &[&[u8]],
236///     program_id: &Address,
237///     is_writable: bool,
238/// ) -> Result<(), ProgramError> {
239///
240#[macro_export]
241macro_rules! require_initialized_pda {
242    ($info:expr, $seeds: expr, $program_id: expr, $is_writable: expr) => {{
243        let pda = match pinocchio::Address::create_program_address($seeds, $program_id) {
244            Ok(pda) => pda,
245            Err(_) => {
246                log!(
247                    "require_initialized_pda!({}, {}, {}, {}); create_program_address failed",
248                    stringify!($info),
249                    stringify!($seeds),
250                    stringify!($program_id),
251                    stringify!($is_writable),
252                );
253                return Err(ProgramError::InvalidSeeds);
254            }
255        };
256        if !address_eq($info.address(), &pda) {
257            log!(
258                "require_initialized_pda!({}, {}, {}, {}); address_eq failed",
259                stringify!($info),
260                stringify!($seeds),
261                stringify!($program_id),
262                stringify!($is_writable)
263            );
264            $info.address().log();
265            $program_id.log();
266            return Err(ProgramError::InvalidSeeds);
267        }
268
269        require_owned_by!($info, $program_id);
270
271        if $is_writable && !$info.is_writable() {
272            log!(
273                "require_initialized_pda!({}, {}, {}, {}); is_writable expectation failed",
274                stringify!($info),
275                stringify!($seeds),
276                stringify!($program_id),
277                stringify!($is_writable)
278            );
279            $info.address().log();
280            return Err(ProgramError::Immutable);
281        }
282    }};
283}
284
285#[macro_export]
286macro_rules! require_initialized_pda_fast {
287    ($info:expr, $seeds: expr, $is_writable: expr) => {{
288        let pda = solana_sha256_hasher::hashv($seeds).to_bytes();
289        if !address_eq($info.address(), &pda.into()) {
290            log!(
291                "require_initialized_pda!({}, {}, {}); address_eq failed",
292                stringify!($info),
293                stringify!($seeds),
294                stringify!($is_writable)
295            );
296            $info.address().log();
297            return Err(ProgramError::InvalidSeeds);
298        }
299
300        require_owned_by!($info, &$crate::fast::ID);
301
302        if $is_writable && !$info.is_writable() {
303            log!(
304                "require_initialized_pda!({}, {}, {}); is_writable expectation failed",
305                stringify!($info),
306                stringify!($seeds),
307                stringify!($is_writable)
308            );
309            $info.address().log();
310            return Err(ProgramError::Immutable);
311        }
312    }};
313}
314
315#[macro_export]
316macro_rules! require_pda {
317    ($info:expr, $seeds: expr, $program_id: expr, $is_writable: expr) => {{
318        let pda = match pinocchio::Address::create_program_address($seeds, $program_id) {
319            Ok(pda) => pda,
320            Err(_) => {
321                log!(
322                    "require_pda!({}, {}, {}, {}); create_program_address failed",
323                    stringify!($info),
324                    stringify!($seeds),
325                    stringify!($program_id),
326                    stringify!($is_writable),
327                );
328                return Err(ProgramError::InvalidSeeds);
329            }
330        };
331        if !address_eq($info.address(), &pda) {
332            log!(
333                "require_pda!({}, {}, {}, {}); address_eq failed",
334                stringify!($info),
335                stringify!($seeds),
336                stringify!($program_id),
337                stringify!($is_writable)
338            );
339            $info.address().log();
340            $program_id.log();
341            return Err(ProgramError::InvalidSeeds);
342        }
343
344        if $is_writable && !$info.is_writable() {
345            log!(
346                "require_pda!({}, {}, {}, {}); is_writable expectation failed",
347                stringify!($info),
348                stringify!($seeds),
349                stringify!($program_id),
350                stringify!($is_writable)
351            );
352            $info.address().log();
353            return Err(ProgramError::Immutable);
354        }
355    }};
356}
357
358/// Errors if:
359/// - Account is not owned by expected program.
360#[inline(always)]
361pub fn require_owned_pda(
362    info: &AccountView,
363    owner: &Address,
364    label: &str,
365) -> Result<(), ProgramError> {
366    if !address_eq(unsafe { info.owner() }, owner) {
367        log!("Invalid account owner for {}:", label);
368        info.address().log();
369        unsafe { info.owner() }.log();
370        owner.log();
371        return Err(ProgramError::InvalidAccountOwner);
372    }
373    Ok(())
374}
375
376/// Errors if:
377/// - Account is not a signer.
378#[inline(always)]
379pub fn require_signer(
380    info: &AccountView,
381    label: &str,
382) -> Result<(), ProgramError> {
383    if !info.is_signer() {
384        log!("Account needs to be signer {}: ", label);
385        info.address().log();
386        return Err(ProgramError::MissingRequiredSignature);
387    }
388
389    Ok(())
390}
391
392/// Errors if:
393/// - Address does not match PDA derived from provided seeds.
394#[inline(always)]
395pub fn require_pda(
396    info: &AccountView,
397    seeds: &[&[u8]],
398    program_id: &Address,
399    is_writable: bool,
400    label: &str,
401) -> Result<u8, ProgramError> {
402    let pda = Address::find_program_address(seeds, program_id);
403
404    if !address_eq(info.address(), &pda.0) {
405        log!("Invalid seeds for {}: ", label);
406        info.address().log();
407        return Err(ProgramError::InvalidSeeds);
408    }
409
410    if is_writable && !info.is_writable() {
411        log!("Account needs to be writable. Label: {}", label);
412        info.address().log();
413        return Err(ProgramError::Immutable);
414    }
415
416    Ok(pda.1)
417}
418
419/// Returns true if the account is uninitialized based on the following conditions:
420/// - Owner is the system program.
421/// - Data is empty.
422pub fn is_uninitialized_account(info: &AccountView) -> bool {
423    address_eq(unsafe { info.owner() }, &pinocchio_system::ID)
424        && info.is_data_empty()
425}
426
427/// Errors if:
428/// - Owner is not the system program.
429/// - Data is not empty.
430/// - Account is not writable.
431#[inline(always)]
432pub fn require_uninitialized_account(
433    info: &AccountView,
434    is_writable: bool,
435    ctx: impl RequireUninitializedAccountCtx,
436) -> Result<(), ProgramError> {
437    if !address_eq(unsafe { info.owner() }, &pinocchio_system::id()) {
438        log!(
439            "Invalid owner for account. Label: {}; account and owner: ",
440            ctx.label()
441        );
442        info.address().log();
443        unsafe { info.owner() }.log();
444        return Err(ctx.invalid_account_owner());
445    }
446
447    if !info.is_data_empty() {
448        log!(
449            "Account needs to be uninitialized. Label: {}, account: ",
450            ctx.label(),
451        );
452        info.address().log();
453        return Err(ctx.account_already_initialized());
454    }
455
456    if is_writable && !info.is_writable() {
457        log!(
458            "Account needs to be writable. label: {}, account: ",
459            ctx.label()
460        );
461        info.address().log();
462        return Err(ctx.immutable());
463    }
464
465    Ok(())
466}
467
468/// Errors if:
469/// - Address does not match PDA derived from provided seeds.
470/// - Cannot load as an uninitialized account.
471#[inline(always)]
472pub fn require_uninitialized_pda(
473    info: &AccountView,
474    seeds: &[&[u8]],
475    program_id: &Address,
476    is_writable: bool,
477    ctx: impl RequireUninitializedAccountCtx,
478) -> Result<u8, ProgramError> {
479    let pda = Address::find_program_address(seeds, program_id);
480
481    if !address_eq(info.address(), &pda.0) {
482        log!("Invalid seeds for account {}: ", ctx.label());
483        info.address().log();
484        return Err(ctx.invalid_seeds());
485    }
486
487    require_uninitialized_account(info, is_writable, ctx)?;
488    Ok(pda.1)
489}
490
491/// Errors if:
492/// - Address does not match PDA derived from provided seeds.
493/// - Owner is not the expected program.
494/// - Account is not writable if set to writable.
495pub fn require_initialized_pda(
496    info: &AccountView,
497    seeds: &[&[u8]],
498    program_id: &Address,
499    is_writable: bool,
500    label: &str,
501) -> Result<u8, ProgramError> {
502    let pda = Address::find_program_address(seeds, program_id);
503    if !address_eq(info.address(), &pda.0) {
504        log!("Invalid seeds (label: {}) for account ", label);
505        info.address().log();
506        return Err(ProgramError::InvalidSeeds);
507    }
508
509    require_owned_pda(info, program_id, label)?;
510
511    if is_writable && !info.is_writable() {
512        log!("Account needs to be writable. label: {}, account: ", label);
513        info.address().log();
514        return Err(ProgramError::Immutable);
515    }
516
517    Ok(pda.1)
518}
519
520/// Errors if:
521/// - Address does not match the expected value.
522/// - Account is not executable.
523#[inline(always)]
524#[allow(dead_code)]
525pub fn require_program(
526    info: &AccountView,
527    key: &Address,
528    label: &str,
529) -> Result<(), ProgramError> {
530    if !address_eq(info.address(), key) {
531        log!("Invalid program account {}: ", label);
532        info.address().log();
533        return Err(ProgramError::IncorrectProgramId);
534    }
535
536    if !info.executable() {
537        log!("{} program is not executable: ", label);
538        info.address().log();
539        return Err(ProgramError::InvalidAccountData);
540    }
541
542    Ok(())
543}
544
545/// Load fee vault PDA
546/// - Protocol fees vault PDA
547pub fn require_initialized_protocol_fees_vault(
548    fees_vault: &AccountView,
549    is_writable: bool,
550) -> Result<(), ProgramError> {
551    require_initialized_pda(
552        fees_vault,
553        &[b"fees-vault"],
554        &crate::fast::ID,
555        is_writable,
556        "protocol fees vault",
557    )?;
558    Ok(())
559}
560
561/// Load validator fee vault PDA
562/// - Validator fees vault PDA must be derived from the validator pubkey
563/// - Validator fees vault PDA must be initialized with the expected seeds and owner
564pub fn require_initialized_validator_fees_vault(
565    validator: &AccountView,
566    validator_fees_vault: &AccountView,
567    is_writable: bool,
568) -> Result<(), ProgramError> {
569    let pda = validator_fees_vault_pda_from_validator(
570        &validator.address().to_bytes().into(),
571    )
572    .to_bytes()
573    .into();
574    if !address_eq(validator_fees_vault.address(), &pda) {
575        log!("Invalid validator fees vault PDA, expected: ");
576        pda.log();
577        log!("but got: ");
578        validator_fees_vault.address().log();
579        return Err(DlpError::InvalidAuthority.into());
580    }
581    require_initialized_pda(
582        validator_fees_vault,
583        &[pda::VALIDATOR_FEES_VAULT_TAG, validator.address().as_ref()],
584        &crate::fast::ID,
585        is_writable,
586        "validator fees vault",
587    )?;
588    Ok(())
589}
590
591/// Load program config PDA
592/// - Program config PDA must be initialized with the expected seeds and owner, or not exists
593pub fn require_program_config(
594    program_config: &AccountView,
595    program: &Address,
596    is_writable: bool,
597) -> Result<bool, ProgramError> {
598    let pda = program_config_from_program_id(&program.to_bytes().into());
599    if !address_eq(&pda.to_bytes().into(), program_config.address()) {
600        log!("Invalid program config PDA, expected: ");
601        pda.log();
602        log!("but got: ");
603        program_config.address().log();
604        return Err(DlpError::InvalidAuthority.into());
605    }
606    require_pda(
607        program_config,
608        &[pda::PROGRAM_CONFIG_TAG, program.as_ref()],
609        &crate::fast::ID,
610        is_writable,
611        "program config",
612    )?;
613    Ok(!address_eq(
614        unsafe { program_config.owner() },
615        &pinocchio_system::ID,
616    ))
617}
618
619/// Load initialized delegation record
620/// - Delegation record must be derived from the delegated account
621pub fn require_initialized_delegation_record(
622    delegated_account: &AccountView,
623    delegation_record: &AccountView,
624    is_writable: bool,
625) -> Result<(), ProgramError> {
626    require_initialized_pda(
627        delegation_record,
628        &[
629            pda::DELEGATION_RECORD_TAG,
630            delegated_account.address().as_ref(),
631        ],
632        &crate::fast::ID,
633        is_writable,
634        "delegation record",
635    )?;
636    Ok(())
637}
638
639/// Load initialized delegation metadata
640/// - Delegation metadata must be derived from the delegated account
641pub fn require_initialized_delegation_metadata(
642    delegated_account: &AccountView,
643    delegation_metadata: &AccountView,
644    is_writable: bool,
645) -> Result<(), ProgramError> {
646    require_initialized_pda(
647        delegation_metadata,
648        &[
649            pda::DELEGATION_METADATA_TAG,
650            delegated_account.address().as_ref(),
651        ],
652        &crate::fast::ID,
653        is_writable,
654        "delegation metadata",
655    )?;
656    Ok(())
657}
658
659/// Load initialized commit state account
660/// - Commit state account must be derived from the delegated account pubkey
661pub fn require_initialized_commit_state(
662    delegated_account: &AccountView,
663    commit_state: &AccountView,
664    is_writable: bool,
665) -> Result<(), ProgramError> {
666    require_initialized_pda(
667        commit_state,
668        &[pda::COMMIT_STATE_TAG, delegated_account.address().as_ref()],
669        &crate::fast::ID,
670        is_writable,
671        "commit state",
672    )?;
673    Ok(())
674}
675
676/// Load initialized commit state record
677/// - Commit record account must be derived from the delegated account pubkey
678pub fn require_initialized_commit_record(
679    delegated_account: &AccountView,
680    commit_record: &AccountView,
681    is_writable: bool,
682) -> Result<(), ProgramError> {
683    require_initialized_pda(
684        commit_record,
685        &[pda::COMMIT_RECORD_TAG, delegated_account.address().as_ref()],
686        &crate::fast::ID,
687        is_writable,
688        "commit record",
689    )?;
690    Ok(())
691}
692
693/// Context for `require_uninitialized_account` / `require_uninitialized_pda`.
694///
695/// This trait describes how to map low–level validation failures for a
696/// particular account (e.g. "commit state account", "delegation record")
697/// into concrete `DlpError` variants.
698pub trait RequireUninitializedAccountCtx {
699    fn label(&self) -> &str;
700    fn invalid_seeds(&self) -> ProgramError;
701    fn invalid_account_owner(&self) -> ProgramError;
702    fn account_already_initialized(&self) -> ProgramError;
703    fn immutable(&self) -> ProgramError;
704}
705
706macro_rules! define_uninitialized_ctx {
707    (
708        $name:ident,
709        label = $label:expr,
710        invalid_seeds = $seeds:expr,
711        invalid_account_owner = $owner:expr,
712        account_already_initialized = $already_init:expr,
713        immutable = $immutable:expr
714    ) => {
715        pub struct $name;
716
717        impl $crate::requires::RequireUninitializedAccountCtx for $name {
718            fn label(&self) -> &str {
719                $label
720            }
721
722            fn invalid_seeds(&self) -> pinocchio::error::ProgramError {
723                $seeds.into()
724            }
725
726            fn invalid_account_owner(&self) -> pinocchio::error::ProgramError {
727                $owner.into()
728            }
729
730            fn account_already_initialized(
731                &self,
732            ) -> pinocchio::error::ProgramError {
733                $already_init.into()
734            }
735
736            fn immutable(&self) -> pinocchio::error::ProgramError {
737                $immutable.into()
738            }
739        }
740    };
741}
742
743define_uninitialized_ctx!(
744    CommitStateAccountCtx,
745    label = "commit state account",
746    invalid_seeds = DlpError::CommitStateInvalidSeeds,
747    invalid_account_owner = DlpError::CommitStateInvalidAccountOwner,
748    account_already_initialized = DlpError::CommitStateAlreadyInitialized,
749    immutable = DlpError::CommitStateImmutable
750);
751
752define_uninitialized_ctx!(
753    CommitRecordCtx,
754    label = "commit record",
755    invalid_seeds = DlpError::CommitRecordInvalidSeeds,
756    invalid_account_owner = DlpError::CommitRecordInvalidAccountOwner,
757    account_already_initialized = DlpError::CommitRecordAlreadyInitialized,
758    immutable = DlpError::CommitRecordImmutable
759);
760
761define_uninitialized_ctx!(
762    DelegationRecordCtx,
763    label = "delegation record",
764    invalid_seeds = DlpError::DelegationRecordInvalidSeeds,
765    invalid_account_owner = DlpError::DelegationRecordInvalidAccountOwner,
766    account_already_initialized = DlpError::DelegationRecordAlreadyInitialized,
767    immutable = DlpError::DelegationRecordImmutable
768);
769
770define_uninitialized_ctx!(
771    DelegationMetadataCtx,
772    label = "delegation metadata",
773    invalid_seeds = DlpError::DelegationMetadataInvalidSeeds,
774    invalid_account_owner = DlpError::DelegationMetadataInvalidAccountOwner,
775    account_already_initialized =
776        DlpError::DelegationMetadataAlreadyInitialized,
777    immutable = DlpError::DelegationMetadataImmutable
778);
779
780define_uninitialized_ctx!(
781    UndelegateBufferCtx,
782    label = "undelegate buffer",
783    invalid_seeds = DlpError::UndelegateBufferInvalidSeeds,
784    invalid_account_owner = DlpError::UndelegateBufferInvalidAccountOwner,
785    account_already_initialized = DlpError::UndelegateBufferAlreadyInitialized,
786    immutable = DlpError::UndelegateBufferImmutable
787);
788
789pub fn require_authorization(
790    program_data: &AccountView,
791    admin: &AccountView,
792) -> Result<(), ProgramError> {
793    #[cfg(feature = "unit_test_config")]
794    {
795        let _ = program_data;
796
797        require_eq_keys!(
798            &Address::from(
799                crate::consts::DEFAULT_VALIDATOR_IDENTITY.to_bytes()
800            ),
801            admin.address(),
802            ProgramError::IncorrectAuthority
803        );
804        Ok(())
805    }
806
807    #[cfg(not(feature = "unit_test_config"))]
808    {
809        // Derive and validate program data address
810        require_eq_keys!(
811            &crate::consts::DELEGATION_PROGRAM_DATA_ID,
812            program_data.address(),
813            ProgramError::IncorrectAuthority
814        );
815
816        //
817        // ref: https://github.com/anza-xyz/solana-sdk/blob/55809cfe/loader-v3-interface/src/state.rs
818        let offset_of_upgrade_authority_address =
819            4 // for the variant ProgramData (u32)
820                + 8 // for slot (u64)
821            ;
822
823        // constants to enhance readability
824        const PROGRAM_DATA: u8 = 3;
825        const OPTION_SOME: u8 = 1;
826
827        //
828        // SAFETY: This authorization logic reads raw ProgramData bytes using the current
829        // Upgradeable Loader v3 layout.
830        let data = program_data.try_borrow()?;
831        if data.len() >= offset_of_upgrade_authority_address + 33
832            && data[0] == PROGRAM_DATA
833            && data[offset_of_upgrade_authority_address] == OPTION_SOME
834        {
835            let bytes = &data[offset_of_upgrade_authority_address + 1
836                ..offset_of_upgrade_authority_address + 33];
837
838            let upgrade_authority_address =
839                unsafe { &*(bytes.as_ptr() as *const Address) };
840
841            require_eq_keys!(
842                upgrade_authority_address,
843                admin.address(),
844                ProgramError::IncorrectAuthority
845            );
846
847            Ok(())
848        } else {
849            Err(ProgramError::InvalidAccountData)
850        }
851    }
852}