1use cosmwasm_std::{to_json_binary, Addr, Coin, Empty, Env, StdResult, Storage, Uint128, WasmMsg};
2use cw20::{Cw20Coin, MinterResponse};
3use cw20_base;
4use cw_multi_test::error::AnyResult;
5use cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor};
6use serde::de::DeserializeOwned;
7use serde::{Deserialize, Serialize};
8use std::fmt::Debug;
9
10pub trait TestingContract<IM, EM, QM, MM = Empty>
12where
13 IM: serde::Serialize,
14 EM: serde::Serialize + Debug,
15 QM: serde::Serialize,
16 MM: serde::Serialize,
17{
18 fn wrapper() -> Box<dyn Contract<Empty>>;
19
20 fn default_init(app: &mut App, env: &Env) -> IM;
21
22 fn new(app: &mut App, env: &Env, msg: Option<IM>) -> Self;
23
24 fn store_code(app: &mut App) -> u64 {
25 app.store_code(Self::wrapper())
26 }
27
28 fn instantiate(app: &mut App, code_id: u64, label: &str, msg: &IM) -> Addr {
29 let admin = app.api().addr_make("admin");
30 let addr = app
31 .instantiate_contract(
32 code_id,
33 app.api().addr_make("sender"),
34 msg,
35 &[],
36 label,
37 Some(admin.to_string()),
38 )
39 .unwrap();
40 Self::set_contract_addr(app, label, &addr);
41 addr
42 }
43
44 fn set_contract_addr(app: &mut App, label: &str, addr: &Addr) {
47 let key = format!("CONTRACT:{}", label);
48 let value = String::from_utf8(addr.as_bytes().to_vec()).unwrap();
49 app.storage_mut().set(key.as_bytes(), value.as_bytes());
50 }
51
52 fn get_contract_addr(app: &App, label: &str) -> Addr {
54 let key = format!("CONTRACT:{}", label);
55 match app.storage().get(key.as_bytes()) {
56 Some(value) => Addr::unchecked(String::from_utf8(value).unwrap()),
57 None => app.api().addr_make(key.as_str()), }
59 }
60
61 fn addr(&self) -> &Addr;
62
63 fn execute(&self, app: &mut App, sender: &Addr, msg: &EM) -> AnyResult<AppResponse> {
64 self.execute_with_funds(app, sender, msg, vec![])
65 }
66
67 fn execute_with_funds(
68 &self,
69 app: &mut App,
70 sender: &Addr,
71 msg: &EM,
72 funds: Vec<Coin>,
73 ) -> AnyResult<AppResponse> {
74 app.execute_contract(sender.clone(), self.addr().clone(), msg, &funds)
75 }
76
77 fn query<T: DeserializeOwned>(&self, app: &App, msg: &QM) -> StdResult<T> {
78 app.wrap().query_wasm_smart(self.addr(), &msg)
79 }
80
81 fn migrate(&self, app: &mut App, sender: &Addr, msg: &MM) -> AnyResult<AppResponse> {
82 let msg_bin = to_json_binary(&msg).expect("cannot serialize MigrateMsg");
83 let code_id = Self::store_code(app);
84 let migrate_msg = WasmMsg::Migrate {
85 contract_addr: self.addr().to_string(),
86 new_code_id: code_id,
87 msg: msg_bin,
88 };
89
90 app.execute(sender.clone(), migrate_msg.into())
91 }
92}
93
94#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
95pub struct Cw20TokenContract {
96 pub addr: Addr,
97 pub init: cw20_base::msg::InstantiateMsg,
98}
99
100impl
101 TestingContract<
102 cw20_base::msg::InstantiateMsg,
103 cw20_base::msg::ExecuteMsg,
104 cw20_base::msg::QueryMsg,
105 > for Cw20TokenContract
106{
107 fn wrapper() -> Box<dyn Contract<Empty>> {
108 Box::new(ContractWrapper::new(
109 cw20_base::contract::execute,
110 cw20_base::contract::instantiate,
111 cw20_base::contract::query,
112 ))
113 }
114
115 fn default_init(app: &mut App, _env: &Env) -> cw20_base::msg::InstantiateMsg {
116 cw20_base::msg::InstantiateMsg {
117 symbol: "SATL".to_string(),
118 name: "Satlayer Test Token".to_string(),
119 decimals: 18,
120 initial_balances: vec![Cw20Coin {
121 address: app.api().addr_make("owner").to_string(),
122 amount: Uint128::new(1_000_000e18 as u128),
123 }],
124 mint: Some(MinterResponse {
125 minter: app.api().addr_make("owner").to_string(),
126 cap: Some(Uint128::new(1_000_000_000e18 as u128)), }),
128 marketing: None,
129 }
130 }
131
132 fn new(app: &mut App, env: &Env, msg: Option<cw20_base::msg::InstantiateMsg>) -> Self {
133 let init = msg.unwrap_or(Self::default_init(app, env));
134 let code_id = Self::store_code(app);
135 let addr = Self::instantiate(app, code_id, "cw20", &init);
136 Self { addr, init }
137 }
138
139 fn addr(&self) -> &Addr {
140 &self.addr
141 }
142}
143
144impl Cw20TokenContract {
145 pub fn increase_allowance(&self, app: &mut App, sender: &Addr, spender: &Addr, amount: u128) {
147 let msg = &cw20_base::msg::ExecuteMsg::IncreaseAllowance {
148 spender: spender.to_string(),
149 amount: Uint128::new(amount),
150 expires: None,
151 };
152 self.execute(app, sender, msg).unwrap();
153 }
154
155 pub fn fund(&self, app: &mut App, recipient: &Addr, amount: u128) {
157 let owner = Addr::unchecked(&self.init.initial_balances[0].address);
158 let msg = &cw20_base::msg::ExecuteMsg::Transfer {
159 recipient: recipient.to_string(),
160 amount: Uint128::new(amount),
161 };
162 self.execute(app, &owner, msg).unwrap();
163 }
164
165 pub fn transfer(&self, app: &mut App, sender: &Addr, recipient: &Addr, amount: u128) {
166 let msg = &cw20_base::msg::ExecuteMsg::Transfer {
167 recipient: recipient.to_string(),
168 amount: Uint128::new(amount),
169 };
170 self.execute(app, sender, msg).unwrap();
171 }
172
173 pub fn balance(&self, app: &App, address: &Addr) -> u128 {
174 let query = cw20_base::msg::QueryMsg::Balance {
175 address: address.to_string(),
176 };
177 let res: cw20::BalanceResponse = self.query(app, &query).unwrap();
178 res.balance.into()
179 }
180}