use std::collections::HashMap;
use crate::coins::Coin;
use crate::encoding::Binary;
use crate::errors::{generic_err, invalid_utf8, StdResult, SystemError};
use crate::query::{
AllBalanceResponse, AllDelegationsResponse, BalanceResponse, BankQuery, BondedDenomResponse,
DelegationResponse, FullDelegation, QueryRequest, StakingQuery, Validator, ValidatorsResponse,
WasmQuery,
};
use crate::serde::{from_slice, to_binary};
use crate::storage::MemoryStorage;
use crate::traits::{Api, Extern, Querier, QuerierResult};
use crate::types::{BlockInfo, CanonicalAddr, ContractInfo, Env, HumanAddr, MessageInfo, Never};
pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract";
pub fn mock_dependencies(
canonical_length: usize,
contract_balance: &[Coin],
) -> Extern<MockStorage, MockApi, MockQuerier> {
let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR);
Extern {
storage: MockStorage::default(),
api: MockApi::new(canonical_length),
querier: MockQuerier::new(&[(&contract_addr, contract_balance)]),
}
}
pub fn mock_dependencies_with_balances(
canonical_length: usize,
balances: &[(&HumanAddr, &[Coin])],
) -> Extern<MockStorage, MockApi, MockQuerier> {
Extern {
storage: MockStorage::default(),
api: MockApi::new(canonical_length),
querier: MockQuerier::new(balances),
}
}
pub type MockStorage = MemoryStorage;
#[derive(Copy, Clone)]
pub struct MockApi {
canonical_length: usize,
}
impl MockApi {
pub fn new(canonical_length: usize) -> Self {
MockApi { canonical_length }
}
}
impl Default for MockApi {
fn default() -> Self {
Self::new(20)
}
}
impl Api for MockApi {
fn canonical_address(&self, human: &HumanAddr) -> StdResult<CanonicalAddr> {
if human.len() < 3 {
return Err(generic_err("Invalid input: human address too short"));
}
if human.len() > self.canonical_length {
return Err(generic_err("Invalid input: human address too long"));
}
let mut out = Vec::from(human.as_str());
let append = self.canonical_length - out.len();
if append > 0 {
out.extend(vec![0u8; append]);
}
Ok(CanonicalAddr(Binary(out)))
}
fn human_address(&self, canonical: &CanonicalAddr) -> StdResult<HumanAddr> {
if canonical.len() != self.canonical_length {
return Err(generic_err(
"Invalid input: canonical address length not correct",
));
}
let trimmed: Vec<u8> = canonical
.as_slice()
.iter()
.cloned()
.filter(|&x| x != 0)
.collect();
let human = String::from_utf8(trimmed).map_err(invalid_utf8)?;
Ok(HumanAddr(human))
}
}
pub fn mock_env<T: Api, U: Into<HumanAddr>>(api: &T, sender: U, sent: &[Coin]) -> Env {
Env {
block: BlockInfo {
height: 12_345,
time: 1_571_797_419,
chain_id: "cosmos-testnet-14002".to_string(),
},
message: MessageInfo {
sender: api.canonical_address(&sender.into()).unwrap(),
sent_funds: sent.to_vec(),
},
contract: ContractInfo {
address: api
.canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR))
.unwrap(),
},
}
}
#[derive(Clone, Default)]
pub struct MockQuerier {
bank: BankQuerier,
staking: StakingQuerier,
wasm: NoWasmQuerier,
}
impl MockQuerier {
pub fn new(balances: &[(&HumanAddr, &[Coin])]) -> Self {
MockQuerier {
bank: BankQuerier::new(balances),
staking: StakingQuerier::default(),
wasm: NoWasmQuerier {},
}
}
pub fn update_balance<U: Into<HumanAddr>>(
&mut self,
addr: U,
balance: Vec<Coin>,
) -> Option<Vec<Coin>> {
self.bank.balances.insert(addr.into(), balance)
}
#[cfg(feature = "staking")]
pub fn with_staking(
&mut self,
denom: &str,
validators: &[crate::query::Validator],
delegations: &[crate::query::FullDelegation],
) {
self.staking = StakingQuerier::new(denom, validators, delegations);
}
}
impl Querier for MockQuerier {
fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {
let request: QueryRequest<Never> = match from_slice(bin_request) {
Ok(v) => v,
Err(e) => {
return Err(SystemError::InvalidRequest {
error: format!("Parsing query request: {}", e),
request: bin_request.into(),
})
}
};
self.handle_query(&request)
}
}
impl MockQuerier {
pub fn handle_query<T>(&self, request: &QueryRequest<T>) -> QuerierResult {
match &request {
QueryRequest::Bank(bank_query) => self.bank.query(bank_query),
QueryRequest::Custom(_) => Err(SystemError::UnsupportedRequest {
kind: "custom".to_string(),
}),
QueryRequest::Staking(staking_query) => self.staking.query(staking_query),
QueryRequest::Wasm(msg) => self.wasm.query(msg),
}
}
}
#[derive(Clone, Default)]
struct NoWasmQuerier {
}
impl NoWasmQuerier {
fn query(&self, request: &WasmQuery) -> QuerierResult {
let addr = match request {
WasmQuery::Smart { contract_addr, .. } => contract_addr,
WasmQuery::Raw { contract_addr, .. } => contract_addr,
}
.clone();
Err(SystemError::NoSuchContract { addr })
}
}
#[derive(Clone, Default)]
pub struct BankQuerier {
balances: HashMap<HumanAddr, Vec<Coin>>,
}
impl BankQuerier {
pub fn new(balances: &[(&HumanAddr, &[Coin])]) -> Self {
let mut map = HashMap::new();
for (addr, coins) in balances.iter() {
map.insert(HumanAddr::from(addr), coins.to_vec());
}
BankQuerier { balances: map }
}
pub fn query(&self, request: &BankQuery) -> QuerierResult {
match request {
BankQuery::Balance { address, denom } => {
let amount = self
.balances
.get(address)
.and_then(|v| v.iter().find(|c| &c.denom == denom).map(|c| c.amount))
.unwrap_or_default();
let bank_res = BalanceResponse {
amount: Coin {
amount,
denom: denom.to_string(),
},
};
Ok(to_binary(&bank_res))
}
BankQuery::AllBalances { address } => {
let bank_res = AllBalanceResponse {
amount: self.balances.get(address).cloned().unwrap_or_default(),
};
Ok(to_binary(&bank_res))
}
}
}
}
#[derive(Clone, Default)]
pub struct StakingQuerier {
denom: String,
validators: Vec<Validator>,
delegations: Vec<FullDelegation>,
}
impl StakingQuerier {
pub fn new(denom: &str, validators: &[Validator], delegations: &[FullDelegation]) -> Self {
StakingQuerier {
denom: denom.to_string(),
validators: validators.to_vec(),
delegations: delegations.to_vec(),
}
}
pub fn query(&self, request: &StakingQuery) -> QuerierResult {
match request {
StakingQuery::BondedDenom {} => {
let res = BondedDenomResponse {
denom: self.denom.clone(),
};
Ok(to_binary(&res))
}
StakingQuery::Validators {} => {
let res = ValidatorsResponse {
validators: self.validators.clone(),
};
Ok(to_binary(&res))
}
StakingQuery::AllDelegations { delegator } => {
let delegations: Vec<_> = self
.delegations
.iter()
.filter(|d| &d.delegator == delegator)
.cloned()
.map(|d| d.into())
.collect();
let res = AllDelegationsResponse { delegations };
Ok(to_binary(&res))
}
StakingQuery::Delegation {
delegator,
validator,
} => {
let delegation = self
.delegations
.iter()
.find(|d| &d.delegator == delegator && &d.validator == validator);
let res = DelegationResponse {
delegation: delegation.cloned(),
};
Ok(to_binary(&res))
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::query::Delegation;
use crate::{coin, coins, from_binary, Decimal, HumanAddr};
#[test]
fn mock_env_arguments() {
let name = HumanAddr("my name".to_string());
let api = MockApi::new(20);
let a = mock_env(&api, "my name", &coins(100, "atom"));
let b = mock_env(&api, &name, &coins(100, "atom"));
let c = mock_env(&api, name, &coins(100, "atom"));
assert_eq!(a, b);
assert_eq!(a, c);
}
#[test]
fn flip_addresses() {
let api = MockApi::new(20);
let human = HumanAddr("shorty".to_string());
let canon = api.canonical_address(&human).unwrap();
assert_eq!(canon.len(), 20);
assert_eq!(&canon.as_slice()[0..6], human.as_str().as_bytes());
assert_eq!(&canon.as_slice()[6..], &[0u8; 14]);
let recovered = api.human_address(&canon).unwrap();
assert_eq!(human, recovered);
}
#[test]
#[should_panic(expected = "length not correct")]
fn human_address_input_length() {
let api = MockApi::new(10);
let input = CanonicalAddr(Binary(vec![61; 11]));
api.human_address(&input).unwrap();
}
#[test]
#[should_panic(expected = "address too short")]
fn canonical_address_min_input_length() {
let api = MockApi::new(10);
let human = HumanAddr("1".to_string());
let _ = api.canonical_address(&human).unwrap();
}
#[test]
#[should_panic(expected = "address too long")]
fn canonical_address_max_input_length() {
let api = MockApi::new(10);
let human = HumanAddr("longer-than-10".to_string());
let _ = api.canonical_address(&human).unwrap();
}
#[test]
fn bank_querier_all_balances() {
let addr = HumanAddr::from("foobar");
let balance = vec![coin(123, "ELF"), coin(777, "FLY")];
let bank = BankQuerier::new(&[(&addr, &balance)]);
let all = bank
.query(&BankQuery::AllBalances {
address: addr.clone(),
})
.unwrap()
.unwrap();
let res: AllBalanceResponse = from_binary(&all).unwrap();
assert_eq!(&res.amount, &balance);
}
#[test]
fn bank_querier_one_balance() {
let addr = HumanAddr::from("foobar");
let balance = vec![coin(123, "ELF"), coin(777, "FLY")];
let bank = BankQuerier::new(&[(&addr, &balance)]);
let fly = bank
.query(&BankQuery::Balance {
address: addr.clone(),
denom: "FLY".to_string(),
})
.unwrap()
.unwrap();
let res: BalanceResponse = from_binary(&fly).unwrap();
assert_eq!(res.amount, coin(777, "FLY"));
let miss = bank
.query(&BankQuery::Balance {
address: addr.clone(),
denom: "MISS".to_string(),
})
.unwrap()
.unwrap();
let res: BalanceResponse = from_binary(&miss).unwrap();
assert_eq!(res.amount, coin(0, "MISS"));
}
#[test]
fn bank_querier_missing_account() {
let addr = HumanAddr::from("foobar");
let balance = vec![coin(123, "ELF"), coin(777, "FLY")];
let bank = BankQuerier::new(&[(&addr, &balance)]);
let all = bank
.query(&BankQuery::AllBalances {
address: HumanAddr::from("elsewhere"),
})
.unwrap()
.unwrap();
let res: AllBalanceResponse = from_binary(&all).unwrap();
assert_eq!(res.amount, vec![]);
let miss = bank
.query(&BankQuery::Balance {
address: HumanAddr::from("elsewhere"),
denom: "ELF".to_string(),
})
.unwrap()
.unwrap();
let res: BalanceResponse = from_binary(&miss).unwrap();
assert_eq!(res.amount, coin(0, "ELF"));
}
#[test]
fn staking_querier_validators() {
let val1 = Validator {
address: HumanAddr::from("validator-one"),
commission: Decimal::percent(1),
max_commission: Decimal::percent(3),
max_change_rate: Decimal::percent(1),
};
let val2 = Validator {
address: HumanAddr::from("validator-two"),
commission: Decimal::permille(15),
max_commission: Decimal::permille(40),
max_change_rate: Decimal::permille(5),
};
let staking = StakingQuerier::new("stake", &[val1.clone(), val2.clone()], &[]);
let raw = staking
.query(&StakingQuery::Validators {})
.unwrap()
.unwrap();
let vals: ValidatorsResponse = from_binary(&raw).unwrap();
assert_eq!(vals.validators, vec![val1, val2]);
}
fn get_all_delegators(staking: &StakingQuerier, delegator: HumanAddr) -> Vec<Delegation> {
let raw = staking
.query(&StakingQuery::AllDelegations { delegator })
.unwrap()
.unwrap();
let dels: AllDelegationsResponse = from_binary(&raw).unwrap();
dels.delegations
}
fn get_delegator(
staking: &StakingQuerier,
delegator: HumanAddr,
validator: HumanAddr,
) -> Option<FullDelegation> {
let raw = staking
.query(&StakingQuery::Delegation {
delegator,
validator,
})
.unwrap()
.unwrap();
let dels: DelegationResponse = from_binary(&raw).unwrap();
dels.delegation
}
#[test]
fn staking_querier_delegations() {
let val1 = HumanAddr::from("validator-one");
let val2 = HumanAddr::from("validator-two");
let user_a = HumanAddr::from("investor");
let user_b = HumanAddr::from("speculator");
let user_c = HumanAddr::from("hodler");
let del1a = FullDelegation {
delegator: user_a.clone(),
validator: val1.clone(),
amount: coin(100, "stake"),
can_redelegate: coin(100, "stake"),
accumulated_rewards: coin(5, "stake"),
};
let del2a = FullDelegation {
delegator: user_a.clone(),
validator: val2.clone(),
amount: coin(500, "stake"),
can_redelegate: coin(500, "stake"),
accumulated_rewards: coin(20, "stake"),
};
let del1b = FullDelegation {
delegator: user_b.clone(),
validator: val1.clone(),
amount: coin(500, "stake"),
can_redelegate: coin(0, "stake"),
accumulated_rewards: coin(0, "stake"),
};
let del2c = FullDelegation {
delegator: user_c.clone(),
validator: val2.clone(),
amount: coin(8888, "stake"),
can_redelegate: coin(4567, "stake"),
accumulated_rewards: coin(900, "stake"),
};
let staking = StakingQuerier::new(
"stake",
&[],
&[del1a.clone(), del1b.clone(), del2a.clone(), del2c.clone()],
);
let dels = get_all_delegators(&staking, user_a.clone());
assert_eq!(dels, vec![del1a.clone().into(), del2a.clone().into()]);
let dels = get_all_delegators(&staking, user_b.clone());
assert_eq!(dels, vec![del1b.clone().into()]);
let dels = get_all_delegators(&staking, user_c.clone());
assert_eq!(dels, vec![del2c.clone().into()]);
let dels = get_all_delegators(&staking, HumanAddr::from("no one"));
assert_eq!(dels, vec![]);
let dels = get_delegator(&staking, user_a.clone(), val1.clone());
assert_eq!(dels, Some(del1a.clone()));
let dels = get_delegator(&staking, user_a.clone(), val2.clone());
assert_eq!(dels, Some(del2a.clone()));
let dels = get_delegator(&staking, user_b.clone(), val1.clone());
assert_eq!(dels, Some(del1b.clone()));
let dels = get_delegator(&staking, user_b.clone(), val2.clone());
assert_eq!(dels, None);
let dels = get_delegator(&staking, user_c.clone(), val1.clone());
assert_eq!(dels, None);
let dels = get_delegator(&staking, user_c.clone(), val2.clone());
assert_eq!(dels, Some(del2c.clone()));
}
}