use cosmwasm_std::{to_json_binary, Addr, Coin, Empty, Env, StdResult, Storage, Uint128, WasmMsg};
use cw20::{Cw20Coin, MinterResponse};
use cw20_base;
use cw_multi_test::error::AnyResult;
use cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
pub trait TestingContract<IM, EM, QM, MM = Empty>
where
IM: serde::Serialize,
EM: serde::Serialize + Debug,
QM: serde::Serialize,
MM: serde::Serialize,
{
fn wrapper() -> Box<dyn Contract<Empty>>;
fn default_init(app: &mut App, env: &Env) -> IM;
fn new(app: &mut App, env: &Env, msg: Option<IM>) -> Self;
fn store_code(app: &mut App) -> u64 {
app.store_code(Self::wrapper())
}
fn instantiate(app: &mut App, code_id: u64, label: &str, msg: &IM) -> Addr {
let admin = app.api().addr_make("admin");
let addr = app
.instantiate_contract(
code_id,
app.api().addr_make("sender"),
msg,
&[],
label,
Some(admin.to_string()),
)
.unwrap();
Self::set_contract_addr(app, label, &addr);
addr
}
fn set_contract_addr(app: &mut App, label: &str, addr: &Addr) {
let key = format!("CONTRACT:{label}");
let value = String::from_utf8(addr.as_bytes().to_vec()).unwrap();
app.storage_mut().set(key.as_bytes(), value.as_bytes());
}
fn get_contract_addr(app: &App, label: &str) -> Addr {
let key = format!("CONTRACT:{label}");
match app.storage().get(key.as_bytes()) {
Some(value) => Addr::unchecked(String::from_utf8(value).unwrap()),
None => app.api().addr_make(key.as_str()), }
}
fn addr(&self) -> &Addr;
fn execute(&self, app: &mut App, sender: &Addr, msg: &EM) -> AnyResult<AppResponse> {
self.execute_with_funds(app, sender, msg, vec![])
}
fn execute_with_funds(
&self,
app: &mut App,
sender: &Addr,
msg: &EM,
funds: Vec<Coin>,
) -> AnyResult<AppResponse> {
app.execute_contract(sender.clone(), self.addr().clone(), msg, &funds)
}
fn query<T: DeserializeOwned>(&self, app: &App, msg: &QM) -> StdResult<T> {
app.wrap().query_wasm_smart(self.addr(), &msg)
}
fn migrate(&self, app: &mut App, sender: &Addr, msg: &MM) -> AnyResult<AppResponse> {
let msg_bin = to_json_binary(&msg).expect("cannot serialize MigrateMsg");
let code_id = Self::store_code(app);
let migrate_msg = WasmMsg::Migrate {
contract_addr: self.addr().to_string(),
new_code_id: code_id,
msg: msg_bin,
};
app.execute(sender.clone(), migrate_msg.into())
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Cw20TokenContract {
pub addr: Addr,
pub init: cw20_base::msg::InstantiateMsg,
}
impl
TestingContract<
cw20_base::msg::InstantiateMsg,
cw20_base::msg::ExecuteMsg,
cw20_base::msg::QueryMsg,
> for Cw20TokenContract
{
fn wrapper() -> Box<dyn Contract<Empty>> {
Box::new(ContractWrapper::new(
cw20_base::contract::execute,
cw20_base::contract::instantiate,
cw20_base::contract::query,
))
}
fn default_init(app: &mut App, _env: &Env) -> cw20_base::msg::InstantiateMsg {
cw20_base::msg::InstantiateMsg {
symbol: "SATL".to_string(),
name: "Satlayer Test Token".to_string(),
decimals: 18,
initial_balances: vec![Cw20Coin {
address: app.api().addr_make("owner").to_string(),
amount: Uint128::new(1_000_000e18 as u128),
}],
mint: Some(MinterResponse {
minter: app.api().addr_make("owner").to_string(),
cap: Some(Uint128::new(1_000_000_000e18 as u128)), }),
marketing: None,
}
}
fn new(app: &mut App, env: &Env, msg: Option<cw20_base::msg::InstantiateMsg>) -> Self {
let init = msg.unwrap_or(Self::default_init(app, env));
let code_id = Self::store_code(app);
let addr = Self::instantiate(app, code_id, "cw20", &init);
Self { addr, init }
}
fn addr(&self) -> &Addr {
&self.addr
}
}
impl Cw20TokenContract {
pub fn increase_allowance(&self, app: &mut App, sender: &Addr, spender: &Addr, amount: u128) {
let msg = &cw20_base::msg::ExecuteMsg::IncreaseAllowance {
spender: spender.to_string(),
amount: Uint128::new(amount),
expires: None,
};
self.execute(app, sender, msg).unwrap();
}
pub fn fund(&self, app: &mut App, recipient: &Addr, amount: u128) {
let owner = Addr::unchecked(&self.init.initial_balances[0].address);
let msg = &cw20_base::msg::ExecuteMsg::Transfer {
recipient: recipient.to_string(),
amount: Uint128::new(amount),
};
self.execute(app, &owner, msg).unwrap();
}
pub fn transfer(&self, app: &mut App, sender: &Addr, recipient: &Addr, amount: u128) {
let msg = &cw20_base::msg::ExecuteMsg::Transfer {
recipient: recipient.to_string(),
amount: Uint128::new(amount),
};
self.execute(app, sender, msg).unwrap();
}
pub fn balance(&self, app: &App, address: &Addr) -> u128 {
let query = cw20_base::msg::QueryMsg::Balance {
address: address.to_string(),
};
let res: cw20::BalanceResponse = self.query(app, &query).unwrap();
res.balance.into()
}
}