1use abstract_std::objects::{ans_host::AnsHostError, AnsAsset, AssetEntry};
5use cosmwasm_std::{to_json_binary, Addr, Coin, CosmosMsg, Deps, Env};
6use cw_asset::Asset;
7use serde::Serialize;
8
9use super::AbstractApi;
10use crate::{
11 ans_resolve::Resolve,
12 cw_helpers::ApiQuery,
13 features::{AbstractNameService, AccountExecutor, AccountIdentification, ModuleIdentification},
14 AbstractSdkError, AbstractSdkResult, AccountAction,
15};
16
17pub trait TransferInterface:
19 AbstractNameService + AccountIdentification + ModuleIdentification
20{
21 fn bank<'a>(&'a self, deps: Deps<'a>) -> Bank<'a, Self> {
39 Bank { base: self, deps }
40 }
41}
42
43impl<T> TransferInterface for T where
44 T: AbstractNameService + AccountIdentification + ModuleIdentification
45{
46}
47
48impl<T: TransferInterface> AbstractApi<T> for Bank<'_, T> {
49 const API_ID: &'static str = "Bank";
50
51 fn base(&self) -> &T {
52 self.base
53 }
54 fn deps(&self) -> Deps {
55 self.deps
56 }
57}
58
59#[derive(Clone)]
77pub struct Bank<'a, T: TransferInterface> {
78 base: &'a T,
79 deps: Deps<'a>,
80}
81
82impl<T: TransferInterface> Bank<'_, T> {
83 pub fn balances(&self, assets: &[AssetEntry]) -> AbstractSdkResult<Vec<Asset>> {
85 assets
86 .iter()
87 .map(|asset| self.balance(asset))
88 .collect::<AbstractSdkResult<Vec<Asset>>>()
89 }
90 pub fn balance(&self, asset: &AssetEntry) -> AbstractSdkResult<Asset> {
92 let resolved_info = asset
93 .resolve(&self.deps.querier, &self.base.ans_host(self.deps)?)
94 .map_err(|error| self.wrap_query_error(error))?;
95 let balance = resolved_info.query_balance(
96 &self.deps.querier,
97 self.base.account(self.deps)?.into_addr(),
98 )?;
99 Ok(Asset::new(resolved_info, balance))
100 }
101
102 pub fn deposit<R: Transferable>(&self, funds: Vec<R>) -> AbstractSdkResult<Vec<CosmosMsg>> {
104 let recipient = self.base.account(self.deps)?.into_addr();
105 let transferable_funds = funds
106 .into_iter()
107 .map(|asset| asset.transferable_asset(self.base, self.deps))
108 .collect::<AbstractSdkResult<Vec<Asset>>>()?;
109 transferable_funds
110 .iter()
111 .map(|asset| asset.transfer_msg(recipient.clone()))
112 .collect::<Result<Vec<_>, _>>()
113 .map_err(Into::into)
114 }
115}
116
117impl<T: TransferInterface + AccountExecutor> Bank<'_, T> {
118 pub fn transfer<R: Transferable>(
160 &self,
161 funds: Vec<R>,
162 recipient: &Addr,
163 ) -> AbstractSdkResult<AccountAction> {
164 let transferable_funds = funds
165 .into_iter()
166 .map(|asset| asset.transferable_asset(self.base, self.deps))
167 .collect::<AbstractSdkResult<Vec<Asset>>>()?;
168 let msgs = transferable_funds
169 .iter()
170 .map(|asset| asset.transfer_msg(recipient.clone()))
171 .collect::<Result<Vec<_>, _>>()?;
172
173 Ok(AccountAction::from_vec(msgs))
174 }
175
176 pub fn withdraw<R: Transferable>(
178 &self,
179 env: &Env,
180 funds: Vec<R>,
181 ) -> AbstractSdkResult<AccountAction> {
182 let recipient = &env.contract.address;
183 self.transfer(funds, recipient)
184 }
185
186 pub fn send<R: Transferable, M: Serialize>(
192 &self,
193 funds: R,
194 recipient: &Addr,
195 message: &M,
196 ) -> AbstractSdkResult<AccountAction> {
197 let transferable_funds = funds.transferable_asset(self.base, self.deps)?;
198
199 let msgs = transferable_funds.send_msg(recipient, to_json_binary(message)?)?;
200
201 Ok(AccountAction::from_vec(vec![msgs]))
202 }
203}
204
205pub trait Transferable {
207 fn transferable_asset<T: AbstractNameService + ModuleIdentification>(
209 self,
210 base: &T,
211 deps: Deps,
212 ) -> AbstractSdkResult<Asset>;
213}
214
215fn transferable_api_error(
217 base: &impl ModuleIdentification,
218 error: AnsHostError,
219) -> AbstractSdkError {
220 AbstractSdkError::ApiQuery {
221 api: "Transferable".to_owned(),
222 module_id: base.module_id().to_owned(),
223 error: Box::new(error.into()),
224 }
225}
226
227impl Transferable for &AnsAsset {
228 fn transferable_asset<T: AbstractNameService + ModuleIdentification>(
229 self,
230 base: &T,
231 deps: Deps,
232 ) -> AbstractSdkResult<Asset> {
233 self.resolve(&deps.querier, &base.ans_host(deps)?)
234 .map_err(|error| transferable_api_error(base, error))
235 }
236}
237
238impl Transferable for AnsAsset {
239 fn transferable_asset<T: AbstractNameService + ModuleIdentification>(
240 self,
241 base: &T,
242 deps: Deps,
243 ) -> AbstractSdkResult<Asset> {
244 self.resolve(&deps.querier, &base.ans_host(deps)?)
245 .map_err(|error| transferable_api_error(base, error))
246 }
247}
248
249impl Transferable for Asset {
250 fn transferable_asset<T: AbstractNameService>(
251 self,
252 _base: &T,
253 _deps: Deps,
254 ) -> AbstractSdkResult<Asset> {
255 Ok(self)
256 }
257}
258
259impl Transferable for Coin {
260 fn transferable_asset<T: AbstractNameService>(
261 self,
262 _base: &T,
263 _deps: Deps,
264 ) -> AbstractSdkResult<Asset> {
265 Ok(Asset::from(self))
266 }
267}
268
269#[cfg(test)]
270mod test {
271 #![allow(clippy::needless_borrows_for_generic_args)]
272 use abstract_testing::mock_env_validated;
273 use abstract_testing::prelude::*;
274 use cosmwasm_std::*;
275
276 use super::*;
277 use crate::apis::traits::test::abstract_api_test;
278 use crate::mock_module::*;
279
280 mod balance {
281 use super::*;
282
283 #[coverage_helper::test]
284 fn balance() {
285 let (mut deps, account, app) = mock_module_setup();
286
287 {
289 let bank: Bank<'_, MockModule> = app.bank(deps.as_ref());
290 let res = bank
291 .balances(&[AssetEntry::new("asset_entry")])
292 .unwrap_err();
293 let AbstractSdkError::ApiQuery {
294 api,
295 module_id,
296 error: _,
297 } = res
298 else {
299 panic!("expected api error");
300 };
301 assert_eq!(api, "Bank");
302 assert_eq!(module_id, app.module_id());
303 }
304
305 let abstr = abstract_testing::prelude::AbstractMockAddrs::new(deps.api);
306 deps.querier = abstract_testing::abstract_mock_querier_builder(deps.api)
308 .with_contract_map_entry(
309 &abstr.ans_host,
310 abstract_std::ans_host::state::ASSET_ADDRESSES,
311 (
312 &AssetEntry::new("asset_entry"),
313 cw_asset::AssetInfo::native("asset"),
314 ),
315 )
316 .build();
317 let recipient: Addr = account.into_addr();
318 let coins: Vec<Coin> = coins(100u128, "asset");
319 deps.querier.bank.update_balance(recipient, coins.clone());
320
321 let bank: Bank<'_, MockModule> = app.bank(deps.as_ref());
322 let res = bank.balances(&[AssetEntry::new("asset_entry")]).unwrap();
323 assert_eq!(res, vec![Asset::native("asset", 100u128)]);
324 }
325 }
326
327 mod transfer_coins {
328 use abstract_std::account::ExecuteMsg;
329
330 use super::*;
331 use crate::{Execution, Executor, ExecutorMsg};
332
333 #[coverage_helper::test]
334 fn transfer_asset_to_sender() {
335 let (deps, account, app) = mock_module_setup();
336
337 let recipient: Addr = Addr::unchecked("recipient");
339 let bank: Bank<'_, MockModule> = app.bank(deps.as_ref());
340 let coins: Vec<Coin> = coins(100u128, "asset");
341 let bank_transfer: AccountAction = bank.transfer(coins.clone(), &recipient).unwrap();
342
343 let executor: Executor<'_, MockModule> = app.executor(deps.as_ref());
344 let account_message: ExecutorMsg = executor.execute(vec![bank_transfer]).unwrap();
345 let response: Response = Response::new().add_message(account_message);
346 let expected_msg = CosmosMsg::Bank(BankMsg::Send {
349 to_address: recipient.to_string(),
350 amount: coins,
351 });
352
353 assert_eq!(
354 response.messages[0].msg,
355 wasm_execute(
356 account.addr(),
357 &ExecuteMsg::<Empty>::Execute {
358 msgs: vec![expected_msg],
359 },
360 vec![],
361 )
362 .unwrap()
363 .into(),
364 );
365 }
366 }
367
368 mod deposit {
371
372 use super::*;
373 use crate::apis::respond::AbstractResponse;
374
375 #[coverage_helper::test]
376 fn deposit() {
377 let (deps, account, app) = mock_module_setup();
378
379 let bank: Bank<'_, MockModule> = app.bank(deps.as_ref());
382 let coins: Vec<Coin> = coins(100u128, "denom");
384 let deposit_msgs: Vec<CosmosMsg> = bank.deposit(coins.clone()).unwrap();
386 let response: Response = app.response("deposit").add_messages(deposit_msgs);
388 let bank_msg: CosmosMsg = CosmosMsg::Bank(BankMsg::Send {
391 to_address: account.addr().to_string(),
392 amount: coins,
393 });
394
395 assert_eq!(response.messages[0].msg, bank_msg);
396 }
397 }
398
399 mod withdraw_coins {
400 use super::*;
401
402 #[coverage_helper::test]
403 fn withdraw_coins() {
404 let (deps, _, app) = mock_module_setup();
405
406 let expected_amount = 100u128;
407 let env = mock_env_validated(deps.api);
408
409 let bank = app.bank(deps.as_ref());
410 let coins = coins(expected_amount, "asset");
411 let actual_res = bank.withdraw(&env, coins.clone());
412
413 let expected_msg: CosmosMsg = CosmosMsg::Bank(BankMsg::Send {
414 to_address: env.contract.address.to_string(),
415 amount: coins,
416 });
417
418 assert_eq!(actual_res.unwrap().messages()[0], expected_msg);
419 }
420 }
421
422 mod send_coins {
423 use super::*;
424
425 use cw20::Cw20ExecuteMsg;
426 use cw_asset::AssetError;
427
428 #[coverage_helper::test]
429 fn send_cw20() {
430 let (deps, _, app) = mock_module_setup();
431
432 let expected_amount = 100u128;
433 let expected_recipient = deps.api.addr_make("recipient");
434
435 let bank = app.bank(deps.as_ref());
436 let hook_msg = Empty {};
437 let asset = deps.api.addr_make("asset");
438 let coin = Asset::cw20(asset.clone(), expected_amount);
439 let actual_res = bank.send(coin, &expected_recipient, &hook_msg);
440
441 let expected_msg: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute {
442 contract_addr: asset.to_string(),
443 msg: to_json_binary(&Cw20ExecuteMsg::Send {
444 contract: expected_recipient.to_string(),
445 amount: expected_amount.into(),
446 msg: to_json_binary(&hook_msg).unwrap(),
447 })
448 .unwrap(),
449 funds: vec![],
450 });
451
452 assert_eq!(actual_res.unwrap().messages()[0], expected_msg);
453 }
454
455 #[coverage_helper::test]
456 fn send_coins() {
457 let (deps, _, app) = mock_module_setup();
458
459 let expected_amount = 100u128;
460 let expected_recipient = deps.api.addr_make("recipient");
461
462 let bank = app.bank(deps.as_ref());
463 let coin = coin(expected_amount, "asset");
464 let hook_msg = Empty {};
465 let actual_res = bank.send(coin, &expected_recipient, &hook_msg);
466
467 assert_eq!(
468 actual_res,
469 Err(AbstractSdkError::Asset(
470 AssetError::UnavailableMethodForNative {
471 method: "send".into(),
472 }
473 )),
474 );
475 }
476 }
477
478 #[coverage_helper::test]
479 fn abstract_api() {
480 let (deps, _, app) = mock_module_setup();
481 let bank = app.bank(deps.as_ref());
482
483 abstract_api_test(bank);
484 }
485}