abstract_std/objects/ownership/
gov_ownable.rs

1#![doc = include_str!("README.md")]
2
3pub use crate::objects::gov_type::{GovAction, GovernanceDetails};
4use crate::{objects::storage_namespaces::OWNERSHIP_STORAGE_KEY, AbstractError};
5
6use cosmwasm_std::{
7    Addr, Attribute, BlockInfo, CustomQuery, DepsMut, QuerierWrapper, StdError, StdResult, Storage,
8};
9use cw_address_like::AddressLike;
10use cw_storage_plus::Item;
11pub use cw_utils::Expiration;
12
13use super::nested_admin::query_top_level_owner;
14
15/// Errors associated with the contract's ownership
16#[derive(thiserror::Error, Debug, PartialEq)]
17pub enum GovOwnershipError {
18    #[error(transparent)]
19    Std(#[from] StdError),
20
21    #[error(transparent)]
22    Abstract(#[from] AbstractError),
23
24    #[error("Contract ownership has been renounced")]
25    NoOwner,
26
27    #[error("Caller is not the contract's current owner")]
28    NotOwner,
29
30    #[error("Caller is not the contract's pending owner")]
31    NotPendingOwner,
32
33    #[error("There isn't a pending ownership transfer")]
34    TransferNotFound,
35
36    #[error("A pending ownership transfer exists but it has expired")]
37    TransferExpired,
38
39    #[error("Cannot transfer ownership to renounced structure. use action::renounce")]
40    TransferToRenounced,
41
42    #[error("Cannot change NFT ownership. Transfer the NFT to change account ownership.")]
43    ChangeOfNftOwned,
44}
45
46/// Storage constant for the contract's ownership
47const OWNERSHIP: Item<Ownership<Addr>> = Item::new(OWNERSHIP_STORAGE_KEY);
48
49/// The contract's ownership info
50#[cosmwasm_schema::cw_serde]
51pub struct Ownership<T: AddressLike> {
52    /// The contract's current owner.
53    pub owner: GovernanceDetails<T>,
54
55    /// The account who has been proposed to take over the ownership.
56    /// `None` if there isn't a pending ownership transfer.
57    pub pending_owner: Option<GovernanceDetails<T>>,
58
59    /// The deadline for the pending owner to accept the ownership.
60    /// `None` if there isn't a pending ownership transfer, or if a transfer
61    /// exists and it doesn't have a deadline.
62    pub pending_expiry: Option<Expiration>,
63}
64
65impl<T: AddressLike> Ownership<T> {
66    /// Serializes the current ownership state as attributes which may
67    /// be used in a message response. Serialization is done according
68    /// to the std::fmt::Display implementation for `T` and
69    /// `cosmwasm_std::Expiration` (for `pending_expiry`). If an
70    /// ownership field has no value, `"none"` will be serialized.
71    ///
72    /// Attribute keys used:
73    ///  - owner
74    ///  - pending_owner
75    ///  - pending_expiry
76    ///
77    /// Callers should take care not to use these keys elsewhere
78    /// in their response as CosmWasm will override reused attribute
79    /// keys.
80    ///
81    /// # Example
82    ///
83    /// ```rust
84    /// use abstract_std::objects::{ownership::Ownership, gov_type::GovernanceDetails};
85    /// use cw_utils::Expiration;
86    /// use cosmwasm_std::Attribute;
87    ///
88    /// let ownership = Ownership {
89    ///         owner: GovernanceDetails::Monarchy{ monarch: "blue".to_owned() },
90    ///         pending_owner: None,
91    ///         pending_expiry: Some(Expiration::Never {})
92    /// };
93    /// assert_eq!(
94    ///     ownership.into_attributes(),
95    ///     vec![
96    ///         Attribute::new("owner", "monarch"),
97    ///         Attribute::new("pending_owner", "none"),
98    ///         Attribute::new("pending_expiry", "expiration: never")
99    ///     ],
100    /// )
101    /// ```
102    pub fn into_attributes(self) -> Vec<Attribute> {
103        fn none_or<T: std::fmt::Display>(or: Option<&T>) -> String {
104            or.map_or_else(|| "none".to_string(), |or| or.to_string())
105        }
106        vec![
107            Attribute::new("owner", self.owner.to_string()),
108            Attribute::new("pending_owner", none_or(self.pending_owner.as_ref())),
109            Attribute::new("pending_expiry", none_or(self.pending_expiry.as_ref())),
110        ]
111    }
112
113    /// Assert current owner supports governance change
114    pub fn assert_owner_can_change(&self) -> Result<(), GovOwnershipError> {
115        if let GovernanceDetails::NFT { .. } = self.owner {
116            return Err(GovOwnershipError::ChangeOfNftOwned);
117        }
118
119        Ok(())
120    }
121}
122
123impl Ownership<Addr> {
124    /// Assert that an account is the contract's current owner.
125    fn assert_owner(
126        &self,
127        querier: &QuerierWrapper,
128        sender: &Addr,
129    ) -> Result<(), GovOwnershipError> {
130        // the contract must have an owner
131        let Some(current_owner) = &self.owner.owner_address(querier) else {
132            return Err(GovOwnershipError::NoOwner);
133        };
134
135        // the sender must be the current owner
136        if sender != current_owner {
137            return Err(GovOwnershipError::NotOwner);
138        }
139
140        Ok(())
141    }
142
143    /// Asserts governance change allowed and account is the contract's current owner.
144    fn assert_nested_sender_can_change_owner(
145        &self,
146        querier: &QuerierWrapper,
147        sender: &Addr,
148    ) -> Result<(), GovOwnershipError> {
149        match &self.owner {
150            GovernanceDetails::SubAccount { account } => {
151                let top_level_owner = query_top_level_owner(querier, account.clone())?;
152                // Verify top level account allows ownership changes
153                // We prevent transfers of current ownership if it's NFT
154                top_level_owner.assert_owner_can_change()?;
155
156                // Assert admin
157                // We are dealing with sub account, so we need to check both account as caller and top level address
158                if self.assert_owner(querier, sender).is_err() {
159                    top_level_owner.assert_owner(querier, sender)?
160                }
161            }
162            _ => {
163                // Verify account allows ownership changes
164                // We prevent transfers of current ownership if it's NFT
165                self.assert_owner_can_change()?;
166
167                // Assert admin
168                self.assert_owner(querier, sender)?;
169            }
170        }
171
172        Ok(())
173    }
174}
175
176/// Set the given address as the contract owner.
177///
178/// This function is only intended to be used only during contract instantiation.
179pub fn initialize_owner(
180    deps: DepsMut,
181    owner: GovernanceDetails<String>,
182) -> Result<Ownership<Addr>, GovOwnershipError> {
183    let ownership = Ownership {
184        owner: owner.verify(deps.as_ref())?,
185        pending_owner: None,
186        pending_expiry: None,
187    };
188    OWNERSHIP.save(deps.storage, &ownership)?;
189    Ok(ownership)
190}
191
192/// Return Ok(true) if the contract has an owner and it's the given address.
193/// Return Ok(false) if the contract doesn't have an owner, of if it does but
194/// it's not the given address.
195/// Return Err if fails to load ownership info from storage.
196pub fn is_owner(store: &dyn Storage, querier: &QuerierWrapper, addr: &Addr) -> StdResult<bool> {
197    let ownership = OWNERSHIP.load(store)?;
198
199    if let Some(owner) = ownership.owner.owner_address(querier) {
200        if *addr == owner {
201            return Ok(true);
202        }
203    }
204
205    Ok(false)
206}
207
208/// Assert that an account is the contract's current owner.
209pub fn assert_nested_owner(
210    store: &dyn Storage,
211    querier: &QuerierWrapper,
212    sender: &Addr,
213) -> Result<(), GovOwnershipError> {
214    let ownership = OWNERSHIP.load(store)?;
215    // If current sender is owner of this account - it's the owner
216    let owner_assertion = ownership.assert_owner(querier, sender);
217    if owner_assertion.is_ok() {
218        #[cfg(feature = "xion")]
219        // If this is a self-owned abstract account, we need to make sure the admin flag is set
220        if let GovernanceDetails::AbstractAccount { .. } = ownership.owner {
221            if let Some(true) = crate::account::state::AUTH_ADMIN.may_load(store)? {
222                return Ok(());
223            } else {
224                return Err(crate::objects::ownership::GovOwnershipError::NotOwner);
225            }
226        }
227        return Ok(());
228    }
229    // Otherwise we need to check top level owner
230    let top_level_ownership = if let GovernanceDetails::SubAccount { account } = ownership.owner {
231        query_top_level_owner(querier, account)?
232    } else {
233        return owner_assertion;
234    };
235    // the contract must have an owner
236    match top_level_ownership.assert_owner(querier, sender) {
237        Ok(_) => {
238            #[cfg(feature = "xion")]
239            // If the top level owner is an abstract account, we need to make sure the admin flag is set
240            if let GovernanceDetails::AbstractAccount { address } = top_level_ownership.owner {
241                if let Ok(true) = crate::account::state::AUTH_ADMIN.query(querier, address) {
242                    return Ok(());
243                } else {
244                    return Err(crate::objects::ownership::GovOwnershipError::NotOwner);
245                }
246            }
247            Ok(())
248        }
249        Err(e) => Err(e),
250    }
251}
252
253/// Update the contract's ownership info based on the given action.
254/// Return the updated ownership.
255pub fn update_ownership(
256    deps: DepsMut,
257    block: &BlockInfo,
258    sender: &Addr,
259    action: GovAction,
260) -> Result<Ownership<Addr>, GovOwnershipError> {
261    match action {
262        GovAction::TransferOwnership { new_owner, expiry } => {
263            transfer_ownership(deps, sender, new_owner, expiry)
264        }
265        GovAction::AcceptOwnership => accept_ownership(deps.storage, &deps.querier, block, sender),
266        GovAction::RenounceOwnership => renounce_ownership(deps.storage, &deps.querier, sender),
267    }
268}
269
270/// Get the current ownership value.
271pub fn get_ownership(storage: &dyn Storage) -> StdResult<Ownership<Addr>> {
272    OWNERSHIP.load(storage)
273}
274
275pub fn query_ownership<Q: CustomQuery>(
276    querier: &QuerierWrapper<Q>,
277    remote_contract: Addr,
278) -> StdResult<Ownership<Addr>> {
279    OWNERSHIP.query(querier, remote_contract)
280}
281
282/// Propose to transfer the contract's ownership to the given address, with an
283/// optional deadline.
284fn transfer_ownership(
285    deps: DepsMut,
286    sender: &Addr,
287    new_owner: GovernanceDetails<String>,
288    expiry: Option<Expiration>,
289) -> Result<Ownership<Addr>, GovOwnershipError> {
290    let new_owner = new_owner.verify(deps.as_ref())?;
291
292    if new_owner.owner_address(&deps.querier).is_none() {
293        return Err(GovOwnershipError::TransferToRenounced {});
294    }
295
296    OWNERSHIP.update(deps.storage, |ownership| {
297        // Check sender and verify governance is not immutable
298        ownership.assert_nested_sender_can_change_owner(&deps.querier, sender)?;
299        // NOTE: We don't validate the expiry, i.e. asserting it is later than
300        // the current block time.
301        //
302        // This is because if the owner submits an invalid expiry, it won't have
303        // any negative effect - it's just that the pending owner won't be able
304        // to accept the ownership.
305        //
306        // By not doing the check, we save a little bit of gas.
307        //
308        // To fix the error, the owner can simply invoke `transfer_ownership`
309        // again with the correct expiry and overwrite the invalid one.
310        Ok(Ownership {
311            pending_owner: Some(new_owner),
312            pending_expiry: expiry,
313            ..ownership
314        })
315    })
316}
317
318/// Accept a pending ownership transfer.
319fn accept_ownership(
320    store: &mut dyn Storage,
321    querier: &QuerierWrapper,
322    block: &BlockInfo,
323    sender: &Addr,
324) -> Result<Ownership<Addr>, GovOwnershipError> {
325    OWNERSHIP.update(store, |ownership| {
326        // there must be an existing ownership transfer
327        let Some(maybe_pending_owner) = ownership.pending_owner else {
328            return Err(GovOwnershipError::TransferNotFound);
329        };
330
331        // If new gov has no owner they cannot accept
332        let Some(pending_owner) = maybe_pending_owner.owner_address(querier) else {
333            // It's most likely burned NFT or corrupted NFT contract after proposal
334            // Make sure to not "renounce" ownership accidentally.
335            //
336            // P.S. GovAction::RenounceOwnership still available to the original owner if that was intentional
337            return Err(GovOwnershipError::TransferNotFound);
338        };
339
340        let is_pending_owner = if sender == pending_owner {
341            true
342        } else if let GovernanceDetails::SubAccount { account, .. } = &maybe_pending_owner {
343            // If not direct owner, need to check top level ownership
344
345            // Check if top level owner of pending is caller
346            query_top_level_owner(querier, account.clone())?
347                .owner
348                .owner_address(querier)
349                .map(|top_sender| top_sender == sender)
350                .unwrap_or_default()
351        } else {
352            false
353        };
354
355        // The sender must be the pending owner
356        if !is_pending_owner {
357            return Err(GovOwnershipError::NotPendingOwner);
358        }
359
360        // if the transfer has a deadline, it must not have been reached
361        if let Some(expiry) = &ownership.pending_expiry {
362            if expiry.is_expired(block) {
363                return Err(GovOwnershipError::TransferExpired);
364            }
365        }
366
367        Ok(Ownership {
368            owner: maybe_pending_owner,
369            pending_owner: None,
370            pending_expiry: None,
371        })
372    })
373}
374
375/// Set the contract's ownership as vacant permanently.
376fn renounce_ownership(
377    store: &mut dyn Storage,
378    querier: &QuerierWrapper,
379    sender: &Addr,
380) -> Result<Ownership<Addr>, GovOwnershipError> {
381    OWNERSHIP.update(store, |ownership| {
382        // Check sender and verify governance is not immutable
383        ownership.assert_nested_sender_can_change_owner(querier, sender)?;
384
385        Ok(Ownership {
386            owner: GovernanceDetails::Renounced {},
387            pending_owner: None,
388            pending_expiry: None,
389        })
390    })
391}
392
393//------------------------------------------------------------------------------
394// Tests
395//------------------------------------------------------------------------------
396
397#[cfg(test)]
398mod tests {
399    use cosmwasm_std::{
400        testing::{mock_dependencies, MockApi},
401        Attribute, Timestamp,
402    };
403
404    use super::*;
405
406    fn mock_govs(mock_api: MockApi) -> [GovernanceDetails<Addr>; 3] {
407        [
408            GovernanceDetails::Monarchy {
409                monarch: mock_api.addr_make("larry"),
410            },
411            GovernanceDetails::Monarchy {
412                monarch: mock_api.addr_make("jake"),
413            },
414            GovernanceDetails::Monarchy {
415                monarch: mock_api.addr_make("pumpkin"),
416            },
417        ]
418    }
419
420    fn mock_block_at_height(height: u64) -> BlockInfo {
421        BlockInfo {
422            height,
423            time: Timestamp::from_seconds(10000),
424            chain_id: "".into(),
425        }
426    }
427
428    #[coverage_helper::test]
429    fn initializing_ownership() {
430        let mut deps = mock_dependencies();
431        let [larry, _, _] = mock_govs(deps.api);
432
433        let ownership = initialize_owner(deps.as_mut(), larry.clone().into()).unwrap();
434
435        // ownership returned is same as ownership stored.
436        assert_eq!(ownership, OWNERSHIP.load(deps.as_ref().storage).unwrap());
437
438        assert_eq!(
439            ownership,
440            Ownership {
441                owner: larry,
442                pending_owner: None,
443                pending_expiry: None,
444            },
445        );
446    }
447
448    #[coverage_helper::test]
449    fn initialize_ownership_no_owner() {
450        let mut deps = mock_dependencies();
451
452        let ownership = initialize_owner(deps.as_mut(), GovernanceDetails::Renounced {}).unwrap();
453        assert_eq!(
454            ownership,
455            Ownership {
456                owner: GovernanceDetails::Renounced {},
457                pending_owner: None,
458                pending_expiry: None,
459            },
460        );
461    }
462
463    #[coverage_helper::test]
464    fn asserting_ownership() {
465        let mut deps = mock_dependencies();
466        let [larry, jake, _] = mock_govs(deps.api);
467        let larry_address = larry.owner_address(&deps.as_ref().querier).unwrap();
468        let jake_address = jake.owner_address(&deps.as_ref().querier).unwrap();
469
470        // case 1. owner has not renounced
471        {
472            initialize_owner(deps.as_mut(), larry.clone().into()).unwrap();
473
474            let res = assert_nested_owner(
475                deps.as_ref().storage,
476                &deps.as_ref().querier,
477                &larry_address,
478            );
479            assert!(res.is_ok());
480
481            let res =
482                assert_nested_owner(deps.as_ref().storage, &deps.as_ref().querier, &jake_address);
483            assert_eq!(res.unwrap_err(), GovOwnershipError::NotOwner);
484        }
485
486        // case 2. owner has renounced
487        {
488            let depsmut = deps.as_mut();
489            renounce_ownership(depsmut.storage, &depsmut.querier, &larry_address).unwrap();
490
491            let res = assert_nested_owner(
492                deps.as_ref().storage,
493                &deps.as_ref().querier,
494                &larry_address,
495            );
496            assert_eq!(res.unwrap_err(), GovOwnershipError::NoOwner);
497        }
498    }
499
500    #[coverage_helper::test]
501    fn transferring_ownership() {
502        let mut deps = mock_dependencies();
503        let [larry, jake, pumpkin] = mock_govs(deps.api);
504        let larry_address = larry.owner_address(&deps.as_ref().querier).unwrap();
505        let jake_address = jake.owner_address(&deps.as_ref().querier).unwrap();
506
507        initialize_owner(deps.as_mut(), larry.clone().into()).unwrap();
508
509        // non-owner cannot transfer ownership
510        {
511            let depsmut = deps.as_mut();
512
513            let err = update_ownership(
514                depsmut,
515                &mock_block_at_height(12345),
516                &jake_address,
517                GovAction::TransferOwnership {
518                    new_owner: pumpkin.clone().into(),
519                    expiry: None,
520                },
521            )
522            .unwrap_err();
523            assert_eq!(err, GovOwnershipError::NotOwner);
524        }
525
526        // owner properly transfers ownership
527        {
528            let ownership = update_ownership(
529                deps.as_mut(),
530                &mock_block_at_height(12345),
531                &larry_address,
532                GovAction::TransferOwnership {
533                    new_owner: pumpkin.clone().into(),
534                    expiry: Some(Expiration::AtHeight(42069)),
535                },
536            )
537            .unwrap();
538            assert_eq!(
539                ownership,
540                Ownership {
541                    owner: larry,
542                    pending_owner: Some(pumpkin),
543                    pending_expiry: Some(Expiration::AtHeight(42069)),
544                },
545            );
546
547            let saved_ownership = OWNERSHIP.load(deps.as_ref().storage).unwrap();
548            assert_eq!(saved_ownership, ownership);
549        }
550    }
551
552    #[coverage_helper::test]
553    fn accepting_ownership() {
554        let mut deps = mock_dependencies();
555        let [larry, jake, pumpkin] = mock_govs(deps.api);
556        let larry_address = larry.owner_address(&deps.as_ref().querier).unwrap();
557        let jake_address = jake.owner_address(&deps.as_ref().querier).unwrap();
558        let pumpkin_address = pumpkin.owner_address(&deps.as_ref().querier).unwrap();
559
560        initialize_owner(deps.as_mut(), larry.clone().into()).unwrap();
561
562        // cannot accept ownership when there isn't a pending ownership transfer
563        {
564            let err = update_ownership(
565                deps.as_mut(),
566                &mock_block_at_height(12345),
567                &pumpkin_address,
568                GovAction::AcceptOwnership,
569            )
570            .unwrap_err();
571            assert_eq!(err, GovOwnershipError::TransferNotFound);
572        }
573
574        transfer_ownership(
575            deps.as_mut(),
576            &larry_address,
577            pumpkin.clone().into(),
578            Some(Expiration::AtHeight(42069)),
579        )
580        .unwrap();
581
582        // non-pending owner cannot accept ownership
583        {
584            let err = update_ownership(
585                deps.as_mut(),
586                &mock_block_at_height(12345),
587                &jake_address,
588                GovAction::AcceptOwnership,
589            )
590            .unwrap_err();
591            assert_eq!(err, GovOwnershipError::NotPendingOwner);
592        }
593
594        // cannot accept ownership if deadline has passed
595        {
596            let err = update_ownership(
597                deps.as_mut(),
598                &mock_block_at_height(69420),
599                &pumpkin_address,
600                GovAction::AcceptOwnership,
601            )
602            .unwrap_err();
603            assert_eq!(err, GovOwnershipError::TransferExpired);
604        }
605
606        // pending owner properly accepts ownership before deadline
607        {
608            let ownership = update_ownership(
609                deps.as_mut(),
610                &mock_block_at_height(10000),
611                &pumpkin_address,
612                GovAction::AcceptOwnership,
613            )
614            .unwrap();
615            assert_eq!(
616                ownership,
617                Ownership {
618                    owner: pumpkin,
619                    pending_owner: None,
620                    pending_expiry: None,
621                },
622            );
623
624            let saved_ownership = OWNERSHIP.load(deps.as_ref().storage).unwrap();
625            assert_eq!(saved_ownership, ownership);
626        }
627    }
628
629    #[coverage_helper::test]
630    fn renouncing_ownership() {
631        let mut deps = mock_dependencies();
632        let [larry, jake, pumpkin] = mock_govs(deps.api);
633        let larry_address = larry.owner_address(&deps.as_ref().querier).unwrap();
634        let jake_address = jake.owner_address(&deps.as_ref().querier).unwrap();
635
636        let ownership = Ownership {
637            owner: larry.clone(),
638            pending_owner: Some(pumpkin),
639            pending_expiry: None,
640        };
641        OWNERSHIP.save(deps.as_mut().storage, &ownership).unwrap();
642
643        // non-owner cannot renounce
644        {
645            let err = update_ownership(
646                deps.as_mut(),
647                &mock_block_at_height(12345),
648                &jake_address,
649                GovAction::RenounceOwnership,
650            )
651            .unwrap_err();
652            assert_eq!(err, GovOwnershipError::NotOwner);
653        }
654
655        // owner properly renounces
656        {
657            let ownership = update_ownership(
658                deps.as_mut(),
659                &mock_block_at_height(12345),
660                &larry_address,
661                GovAction::RenounceOwnership,
662            )
663            .unwrap();
664
665            // ownership returned is same as ownership stored.
666            assert_eq!(ownership, OWNERSHIP.load(deps.as_ref().storage).unwrap());
667
668            assert_eq!(
669                ownership,
670                Ownership {
671                    owner: GovernanceDetails::Renounced {},
672                    pending_owner: None,
673                    pending_expiry: None,
674                },
675            );
676        }
677
678        // cannot renounce twice
679        {
680            let err = update_ownership(
681                deps.as_mut(),
682                &mock_block_at_height(12345),
683                &larry_address,
684                GovAction::RenounceOwnership,
685            )
686            .unwrap_err();
687            assert_eq!(err, GovOwnershipError::NoOwner);
688        }
689    }
690
691    #[coverage_helper::test]
692    fn into_attributes_works() {
693        use cw_utils::Expiration;
694        assert_eq!(
695            Ownership {
696                owner: GovernanceDetails::Monarchy {
697                    monarch: "batman".to_string()
698                },
699                pending_owner: None,
700                pending_expiry: Some(Expiration::Never {})
701            }
702            .into_attributes(),
703            vec![
704                Attribute::new("owner", "monarch"),
705                Attribute::new("pending_owner", "none"),
706                Attribute::new("pending_expiry", "expiration: never")
707            ],
708        );
709    }
710}