abstract_adapter/endpoints/
execute.rs

1use abstract_sdk::{
2    base::{ExecuteEndpoint, Handler, IbcCallbackEndpoint, ModuleIbcEndpoint},
3    features::ModuleIdentification,
4    AbstractResponse, AccountVerification,
5};
6use abstract_std::{
7    account::state::ACCOUNT_MODULES,
8    adapter::{AdapterBaseMsg, AdapterExecuteMsg, AdapterRequestMsg, BaseExecuteMsg, ExecuteMsg},
9    objects::ownership::nested_admin::query_top_level_owner_addr,
10};
11use cosmwasm_std::{Addr, Deps, DepsMut, Env, MessageInfo, QuerierWrapper, Response, StdResult};
12use schemars::JsonSchema;
13use serde::Serialize;
14
15use crate::{
16    error::AdapterError,
17    state::{AdapterContract, ContractError, MAXIMUM_AUTHORIZED_ADDRESSES},
18    AdapterResult,
19};
20
21impl<
22        Error: ContractError,
23        CustomInitMsg,
24        CustomExecMsg: Serialize + JsonSchema + AdapterExecuteMsg,
25        CustomQueryMsg,
26        SudoMsg,
27    > ExecuteEndpoint
28    for AdapterContract<Error, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
29{
30    type ExecuteMsg = ExecuteMsg<CustomExecMsg>;
31
32    fn execute(
33        mut self,
34        deps: DepsMut,
35        env: Env,
36        info: MessageInfo,
37        msg: Self::ExecuteMsg,
38    ) -> Result<Response, Error> {
39        match msg {
40            ExecuteMsg::Module(request) => self.handle_app_msg(deps, env, info, request),
41            ExecuteMsg::Base(exec_msg) => self
42                .base_execute(deps, env, info, exec_msg)
43                .map_err(From::from),
44            ExecuteMsg::IbcCallback(msg) => self.ibc_callback(deps, env, info, msg),
45            ExecuteMsg::ModuleIbc(msg) => self.module_ibc(deps, env, info, msg),
46        }
47    }
48}
49
50fn is_top_level_owner(querier: &QuerierWrapper, account: Addr, sender: &Addr) -> StdResult<bool> {
51    let owner = query_top_level_owner_addr(querier, account)?;
52    Ok(owner == sender)
53}
54
55/// The api-contract base implementation.
56impl<Error: ContractError, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
57    AdapterContract<Error, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
58{
59    fn base_execute(
60        &mut self,
61        deps: DepsMut,
62        env: Env,
63        info: MessageInfo,
64        message: BaseExecuteMsg,
65    ) -> AdapterResult {
66        let BaseExecuteMsg {
67            account_address,
68            msg,
69        } = message;
70        let account_registry = self.account_registry(deps.as_ref())?;
71        let account = account_registry
72            .assert_is_account_admin(&env, &info.sender)
73            .map_err(|_| AdapterError::UnauthorizedAdapterRequest {
74                adapter: self.module_id().to_string(),
75                sender: info.sender.to_string(),
76            })
77            .or_else(|e| {
78                // If the sender is not an account or doesn't have the admin functionality enabled, the sender must be a top-level account owner
79                match account_address {
80                    Some(requested_account) => {
81                        let account_address = deps.api.addr_validate(&requested_account)?;
82                        let account = account_registry.assert_is_account(&account_address)?;
83                        if is_top_level_owner(&deps.querier, account.addr().clone(), &info.sender)
84                            .unwrap_or(false)
85                        {
86                            Ok(account)
87                        } else {
88                            Err(AdapterError::UnauthorizedAdapterRequest {
89                                adapter: self.module_id().to_string(),
90                                sender: info.sender.to_string(),
91                            })
92                        }
93                    }
94                    // If not provided the sender must be the direct owner AND have admin execution rights
95                    None => Err(e),
96                }
97            })?;
98
99        self.target_account = Some(account);
100        match msg {
101            AdapterBaseMsg::UpdateAuthorizedAddresses { to_add, to_remove } => {
102                self.update_authorized_addresses(deps, info, to_add, to_remove)
103            }
104        }
105    }
106
107    /// Handle a custom execution message sent to this api.
108    /// Two success scenarios are possible:
109    /// 1. The sender is an authorized address of the given account address and has provided the account address in the message.
110    /// 2. The sender is a account of the given account address.
111    fn handle_app_msg(
112        mut self,
113        deps: DepsMut,
114        env: Env,
115        info: MessageInfo,
116        request: AdapterRequestMsg<CustomExecMsg>,
117    ) -> Result<Response, Error> {
118        let sender = &info.sender;
119        let unauthorized_sender = || AdapterError::UnauthorizedAddressAdapterRequest {
120            adapter: self.module_id().to_string(),
121            sender: sender.to_string(),
122        };
123
124        let account_registry = self.account_registry(deps.as_ref())?;
125
126        let account = match request.account_address {
127            // The sender must either be an authorized address or account.
128            Some(requested_account) => {
129                let account_address = deps.api.addr_validate(&requested_account)?;
130                let requested_core = account_registry.assert_is_account(&account_address)?;
131
132                if requested_core.addr() == sender {
133                    // If the caller is the account of the indicated account_address, it's authorized to do the operation
134                    // This covers the case where the account field of the request is indicated where it doesn't need to be
135                    requested_core
136                } else {
137                    // If not, we load the authorized addresses for the given account address.
138                    let authorized = self
139                        .authorized_addresses
140                        .load(deps.storage, account_address)
141                        .unwrap_or_default();
142                    if authorized.contains(sender)
143                        || is_top_level_owner(&deps.querier, requested_core.addr().clone(), sender)
144                            .unwrap_or(false)
145                    {
146                        // If the sender is an authorized address,
147                        // or top level account return the account.
148                        requested_core
149                    } else {
150                        // If not, we error, this call is not permitted
151                        return Err(unauthorized_sender().into());
152                    }
153                }
154            }
155            None => account_registry
156                .assert_is_account(sender)
157                .map_err(|_| unauthorized_sender())?,
158        };
159        self.target_account = Some(account);
160        self.execute_handler()?(deps, env, info, self, request.request)
161    }
162
163    /// Update authorized addresses from the adapter.
164    fn update_authorized_addresses(
165        &self,
166        deps: DepsMut,
167        info: MessageInfo,
168        to_add: Vec<String>,
169        to_remove: Vec<String>,
170    ) -> AdapterResult {
171        let account = self.target_account.as_ref().unwrap();
172        let account_addr = account.addr().clone();
173
174        let mut authorized_addrs = self
175            .authorized_addresses
176            .may_load(deps.storage, account_addr.clone())?
177            .unwrap_or_default();
178
179        // Handle the addition of authorized addresses
180        for authorized in to_add {
181            // authorized here can either be a contract address or a module id
182            let authorized_addr = get_addr_from_module_id_or_addr(
183                deps.as_ref(),
184                info.sender.clone(),
185                authorized.clone(),
186            )?;
187
188            if authorized_addrs.contains(&authorized_addr) {
189                return Err(AdapterError::AuthorizedAddressOrModuleIdAlreadyPresent {
190                    addr_or_module_id: authorized,
191                });
192            } else {
193                authorized_addrs.push(authorized_addr);
194            }
195        }
196
197        // Handling the removal of authorized addresses
198        for deauthorized in to_remove {
199            let deauthorized_addr = get_addr_from_module_id_or_addr(
200                deps.as_ref(),
201                info.sender.clone(),
202                deauthorized.clone(),
203            )?;
204            if !authorized_addrs.contains(&deauthorized_addr) {
205                return Err(AdapterError::AuthorizedAddressOrModuleIdNotPresent {
206                    addr_or_module_id: deauthorized,
207                });
208            } else {
209                authorized_addrs.retain(|addr| deauthorized_addr.ne(addr));
210            }
211        }
212
213        if authorized_addrs.len() > MAXIMUM_AUTHORIZED_ADDRESSES as usize {
214            return Err(AdapterError::TooManyAuthorizedAddresses {
215                max: MAXIMUM_AUTHORIZED_ADDRESSES,
216            });
217        }
218
219        self.authorized_addresses
220            .save(deps.storage, account_addr.clone(), &authorized_addrs)?;
221        Ok(self.custom_response(
222            "update_authorized_addresses",
223            vec![("account", account_addr.as_str())],
224        ))
225    }
226}
227
228/// This function is a helper to get a contract address from a module ir or from an address.
229/// This is a temporary fix until we change or get rid of the UpdateAuthorizedAddresses API
230fn get_addr_from_module_id_or_addr(
231    deps: Deps,
232    account: Addr,
233    addr_or_module_id: String,
234) -> Result<Addr, AdapterError> {
235    // authorized here can either be a contract address or a module id
236    if let Ok(Some(addr)) = ACCOUNT_MODULES.query(&deps.querier, account, &addr_or_module_id) {
237        // In case we receive a module id
238        Ok(addr)
239    } else if let Ok(addr) = deps.api.addr_validate(addr_or_module_id.as_str()) {
240        // In case we receive an address
241        Ok(addr)
242    } else {
243        Err(AdapterError::AuthorizedAddressOrModuleIdNotValid { addr_or_module_id })
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use abstract_std::adapter;
250    use abstract_testing::prelude::*;
251    use cosmwasm_std::{testing::*, Addr, Storage};
252
253    use super::*;
254    use crate::mock::{mock_init, AdapterMockResult, MockError, MockExecMsg, MOCK_ADAPTER};
255
256    fn execute_as(
257        deps: &mut MockDeps,
258        sender: &Addr,
259        msg: ExecuteMsg<MockExecMsg>,
260    ) -> Result<Response, MockError> {
261        let env = mock_env_validated(deps.api);
262        MOCK_ADAPTER.execute(deps.as_mut(), env, message_info(sender, &[]), msg)
263    }
264
265    fn base_execute_as(
266        deps: &mut MockDeps,
267        sender: &Addr,
268        msg: BaseExecuteMsg,
269    ) -> Result<Response, MockError> {
270        execute_as(deps, sender, adapter::ExecuteMsg::Base(msg))
271    }
272
273    mod update_authorized_addresses {
274        use super::*;
275        use crate::mock::TEST_AUTHORIZED_ADDR;
276
277        fn load_test_account_authorized_addresses(
278            storage: &dyn Storage,
279            account_addr: &Addr,
280        ) -> Vec<Addr> {
281            MOCK_ADAPTER
282                .authorized_addresses
283                .load(storage, account_addr.clone())
284                .unwrap()
285        }
286
287        #[coverage_helper::test]
288        fn authorize_address() -> AdapterMockResult {
289            let mut deps = mock_dependencies();
290            let account = test_account(deps.api);
291            deps.querier = abstract_mock_querier_builder(deps.api)
292                .account(&account, TEST_ACCOUNT_ID)
293                .set_account_admin_call_to(&account)
294                .build();
295
296            mock_init(&mut deps)?;
297
298            let msg = BaseExecuteMsg {
299                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
300                    to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
301                    to_remove: vec![],
302                },
303                account_address: None,
304            };
305
306            base_execute_as(&mut deps, account.addr(), msg)?;
307
308            let api = MOCK_ADAPTER;
309            assert!(!api.authorized_addresses.is_empty(&deps.storage));
310
311            let test_account_authorized_addrs =
312                load_test_account_authorized_addresses(&deps.storage, account.addr());
313
314            assert_eq!(test_account_authorized_addrs.len(), 1);
315            assert!(
316                test_account_authorized_addrs.contains(&deps.api.addr_make(TEST_AUTHORIZED_ADDR))
317            );
318            Ok(())
319        }
320
321        #[coverage_helper::test]
322        fn revoke_address_authorization() -> AdapterMockResult {
323            let mut deps = mock_dependencies();
324            let account = test_account(deps.api);
325            deps.querier = abstract_mock_querier_builder(deps.api)
326                .account(&account, TEST_ACCOUNT_ID)
327                .set_account_admin_call_to(&account)
328                .build();
329
330            mock_init(&mut deps)?;
331
332            let _api = MOCK_ADAPTER;
333            let msg = BaseExecuteMsg {
334                account_address: None,
335                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
336                    to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
337                    to_remove: vec![],
338                },
339            };
340
341            base_execute_as(&mut deps, account.addr(), msg)?;
342
343            let authorized_addrs =
344                load_test_account_authorized_addresses(&deps.storage, account.addr());
345            assert_eq!(authorized_addrs.len(), 1);
346
347            let msg = BaseExecuteMsg {
348                account_address: None,
349                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
350                    to_add: vec![],
351                    to_remove: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
352                },
353            };
354
355            base_execute_as(&mut deps, account.addr(), msg)?;
356            let authorized_addrs =
357                load_test_account_authorized_addresses(&deps.storage, account.addr());
358            assert!(authorized_addrs.is_empty());
359            Ok(())
360        }
361
362        #[coverage_helper::test]
363        fn add_existing_authorized_address() -> AdapterMockResult {
364            let mut deps = mock_dependencies();
365            let account = test_account(deps.api);
366            deps.querier = abstract_mock_querier_builder(deps.api)
367                .account(&account, TEST_ACCOUNT_ID)
368                .set_account_admin_call_to(&account)
369                .build();
370
371            mock_init(&mut deps)?;
372
373            let msg = BaseExecuteMsg {
374                account_address: None,
375                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
376                    to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
377                    to_remove: vec![],
378                },
379            };
380
381            base_execute_as(&mut deps, account.addr(), msg)?;
382
383            let msg = BaseExecuteMsg {
384                account_address: None,
385                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
386                    to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
387                    to_remove: vec![],
388                },
389            };
390
391            let res = base_execute_as(&mut deps, account.addr(), msg);
392
393            assert!(matches!(
394                res,
395                Err(MockError::Adapter(
396                    AdapterError::AuthorizedAddressOrModuleIdAlreadyPresent {
397                        addr_or_module_id: _test_authorized_address_string
398                    }
399                ))
400            ));
401
402            Ok(())
403        }
404
405        #[coverage_helper::test]
406        fn add_module_id_authorized_address() -> AdapterMockResult {
407            let mut deps = mock_dependencies();
408            let account = test_account(deps.api);
409            deps.querier = abstract_mock_querier_builder(deps.api)
410                .account(&account, TEST_ACCOUNT_ID)
411                .set_account_admin_call_to(&account)
412                .build();
413            let abstr = AbstractMockAddrs::new(deps.api);
414
415            mock_init(&mut deps)?;
416
417            let _api = MOCK_ADAPTER;
418            let msg = BaseExecuteMsg {
419                account_address: None,
420                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
421                    to_add: vec![TEST_MODULE_ID.into()],
422                    to_remove: vec![],
423                },
424            };
425
426            base_execute_as(&mut deps, account.addr(), msg)?;
427
428            let authorized_addrs =
429                load_test_account_authorized_addresses(&deps.storage, account.addr());
430            assert_eq!(authorized_addrs.len(), 1);
431            assert_eq!(
432                authorized_addrs[0].to_string(),
433                abstr.module_address.to_string()
434            );
435
436            Ok(())
437        }
438
439        #[coverage_helper::test]
440        fn remove_authorized_address_dne() -> AdapterMockResult {
441            let mut deps = mock_dependencies();
442            let account = test_account(deps.api);
443            deps.querier = abstract_mock_querier_builder(deps.api)
444                .account(&account, TEST_ACCOUNT_ID)
445                .set_account_admin_call_to(&account)
446                .build();
447
448            mock_init(&mut deps)?;
449            let test_authorized_address_string =
450                deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string();
451
452            let _api = MOCK_ADAPTER;
453            let msg = BaseExecuteMsg {
454                account_address: None,
455                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
456                    to_add: vec![],
457                    to_remove: vec![test_authorized_address_string.clone()],
458                },
459            };
460
461            let res = base_execute_as(&mut deps, account.addr(), msg);
462
463            assert_eq!(
464                res,
465                Err(MockError::Adapter(
466                    AdapterError::AuthorizedAddressOrModuleIdNotPresent {
467                        addr_or_module_id: test_authorized_address_string
468                    }
469                ))
470            );
471            Ok(())
472        }
473    }
474
475    mod execute_app {
476        use super::*;
477
478        use crate::mock::TEST_AUTHORIZED_ADDR;
479        use abstract_std::{
480            objects::{account::AccountTrace, AccountId},
481            registry::Account,
482        };
483        use cosmwasm_std::OwnedDeps;
484
485        /// This sets up the test with the following:
486        /// TEST_ACCOUNT has a single authorized address, test_authorized_address
487        ///
488        /// Note that the querier needs to mock the Account base, as the account will
489        /// query the Account base to get the list of authorized addresses.
490        fn setup_with_authorized_addresses(
491            deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>,
492            authorized: Vec<&str>,
493        ) {
494            mock_init(deps).unwrap();
495
496            let msg = BaseExecuteMsg {
497                account_address: None,
498                msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
499                    to_add: authorized
500                        .into_iter()
501                        .map(|addr| deps.api.addr_make(addr).to_string())
502                        .collect(),
503                    to_remove: vec![],
504                },
505            };
506
507            let account = test_account(deps.api);
508            base_execute_as(deps, account.addr(), msg).unwrap();
509        }
510
511        #[coverage_helper::test]
512        fn unauthorized_addresses_are_unauthorized() {
513            let mut deps = mock_dependencies();
514            deps.querier = MockQuerierBuilder::new(deps.api)
515                .account(&test_account(deps.api), TEST_ACCOUNT_ID)
516                .set_account_admin_call_to(&test_account(deps.api))
517                .build();
518
519            setup_with_authorized_addresses(&mut deps, vec![]);
520
521            let msg = ExecuteMsg::Module(AdapterRequestMsg {
522                account_address: None,
523                request: MockExecMsg {},
524            });
525
526            let unauthorized = deps.api.addr_make("someoone");
527            let res = execute_as(&mut deps, &unauthorized, msg);
528
529            assert_unauthorized(res);
530        }
531
532        fn assert_unauthorized(res: Result<Response, MockError>) {
533            assert!(matches!(
534                res,
535                Err(MockError::Adapter(
536                    AdapterError::UnauthorizedAddressAdapterRequest {
537                        sender: _unauthorized,
538                        ..
539                    }
540                ))
541            ));
542        }
543
544        #[coverage_helper::test]
545        fn executing_as_account_account_is_allowed() {
546            let mut deps = mock_dependencies();
547            let account = test_account(deps.api);
548            deps.querier = MockQuerierBuilder::new(deps.api)
549                .account(&account, TEST_ACCOUNT_ID)
550                .set_account_admin_call_to(&account)
551                .build();
552
553            setup_with_authorized_addresses(&mut deps, vec![]);
554
555            let msg = ExecuteMsg::Module(AdapterRequestMsg {
556                account_address: None,
557                request: MockExecMsg {},
558            });
559
560            let res = execute_as(&mut deps, account.addr(), msg);
561
562            assert!(res.is_ok());
563        }
564
565        #[coverage_helper::test]
566        fn executing_as_authorized_address_not_allowed_without_account() {
567            let mut deps = mock_dependencies();
568            deps.querier = MockQuerierBuilder::new(deps.api)
569                .account(&test_account(deps.api), TEST_ACCOUNT_ID)
570                .set_account_admin_call_to(&test_account(deps.api))
571                .build();
572
573            setup_with_authorized_addresses(&mut deps, vec![TEST_AUTHORIZED_ADDR]);
574
575            let msg = ExecuteMsg::Module(AdapterRequestMsg {
576                account_address: None,
577                request: MockExecMsg {},
578            });
579
580            let authorized = deps.api.addr_make(TEST_AUTHORIZED_ADDR);
581            let res = execute_as(&mut deps, &authorized, msg);
582
583            assert_unauthorized(res);
584        }
585
586        #[coverage_helper::test]
587        fn executing_as_authorized_address_is_allowed_via_account() {
588            let mut deps = mock_dependencies();
589            let account = test_account(deps.api);
590            deps.querier = MockQuerierBuilder::new(deps.api)
591                .account(&account, TEST_ACCOUNT_ID)
592                .set_account_admin_call_to(&account)
593                .build();
594
595            setup_with_authorized_addresses(&mut deps, vec![TEST_AUTHORIZED_ADDR]);
596
597            let msg = ExecuteMsg::Module(AdapterRequestMsg {
598                account_address: Some(account.addr().to_string()),
599                request: MockExecMsg {},
600            });
601
602            let authorized = deps.api.addr_make(TEST_AUTHORIZED_ADDR);
603            let res = execute_as(&mut deps, &authorized, msg);
604
605            assert!(res.is_ok());
606        }
607
608        #[coverage_helper::test]
609        fn executing_as_authorized_address_on_diff_account_should_err() {
610            let mut deps = mock_dependencies();
611            let account = test_account(deps.api);
612            let another_account = Account::new(deps.api.addr_make("some_other_account"));
613            deps.querier = MockQuerierBuilder::new(deps.api)
614                .account(&account, TEST_ACCOUNT_ID)
615                .account(
616                    &another_account,
617                    AccountId::new(69420u32, AccountTrace::Local).unwrap(),
618                )
619                .set_account_admin_call_to(&account)
620                .build();
621
622            setup_with_authorized_addresses(&mut deps, vec![TEST_AUTHORIZED_ADDR]);
623
624            let msg = ExecuteMsg::Module(AdapterRequestMsg {
625                account_address: Some(another_account.addr().to_string()),
626                request: MockExecMsg {},
627            });
628
629            let authorized = deps.api.addr_make(TEST_AUTHORIZED_ADDR);
630            let res = execute_as(&mut deps, &authorized, msg);
631
632            assert_unauthorized(res);
633        }
634    }
635}