use schemars::JsonSchema;
use std::fmt;
use cosmwasm_std::{
log, to_binary, Api, BankMsg, Binary, CanonicalAddr, Coin, CosmosMsg, Empty, Env, Extern,
HandleResponse, HumanAddr, InitResponse, Order, Querier, StakingMsg, StdError, StdResult,
Storage,
};
use cw0::Expiration;
use cw1::CanSendResponse;
use cw1_whitelist::{
contract::{handle_freeze, handle_update_admins, init as whitelist_init, query_admin_list},
msg::InitMsg,
state::admin_list_read,
};
use cw2::set_contract_version;
use crate::msg::{
AllAllowancesResponse, AllPermissionsResponse, AllowanceInfo, HandleMsg, PermissionsInfo,
QueryMsg,
};
use crate::state::{
allowances, allowances_read, permissions, permissions_read, Allowance, PermissionErr,
Permissions,
};
use std::ops::{AddAssign, Sub};
const CONTRACT_NAME: &str = "crates.io:cw1-subkeys";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn init<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
msg: InitMsg,
) -> StdResult<InitResponse> {
let result = whitelist_init(deps, env, msg)?;
set_contract_version(&mut deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Ok(result)
}
pub fn handle<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
msg: HandleMsg<Empty>,
) -> StdResult<HandleResponse<Empty>> {
match msg {
HandleMsg::Execute { msgs } => handle_execute(deps, env, msgs),
HandleMsg::Freeze {} => handle_freeze(deps, env),
HandleMsg::UpdateAdmins { admins } => handle_update_admins(deps, env, admins),
HandleMsg::IncreaseAllowance {
spender,
amount,
expires,
} => handle_increase_allowance(deps, env, spender, amount, expires),
HandleMsg::DecreaseAllowance {
spender,
amount,
expires,
} => handle_decrease_allowance(deps, env, spender, amount, expires),
HandleMsg::SetPermissions {
spender,
permissions,
} => handle_set_permissions(deps, env, spender, permissions),
}
}
pub fn handle_execute<S: Storage, A: Api, Q: Querier, T>(
deps: &mut Extern<S, A, Q>,
env: Env,
msgs: Vec<CosmosMsg<T>>,
) -> StdResult<HandleResponse<T>>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
let cfg = admin_list_read(&deps.storage).load()?;
let owner_raw = &deps.api.canonical_address(&env.message.sender)?;
if cfg.is_admin(owner_raw) {
let mut res = HandleResponse::default();
res.messages = msgs;
res.log = vec![log("action", "execute"), log("owner", env.message.sender)];
Ok(res)
} else {
for msg in &msgs {
match msg {
CosmosMsg::Staking(staking_msg) => {
let permissions = permissions(&mut deps.storage);
let perm = permissions.may_load(owner_raw.as_slice())?;
let perm =
perm.ok_or_else(|| StdError::not_found("No permissions for this account"))?;
check_staking_permissions(staking_msg, perm)?;
}
CosmosMsg::Bank(BankMsg::Send {
from_address: _,
to_address: _,
amount,
}) => {
let mut allowances = allowances(&mut deps.storage);
let allow = allowances.may_load(owner_raw.as_slice())?;
let mut allowance = allow
.ok_or_else(|| StdError::not_found("No allowance for this account"))?;
allowance.balance = allowance.balance.sub(amount.clone())?;
allowances.save(owner_raw.as_slice(), &allowance)?;
}
_ => {
return Err(StdError::generic_err("Message type rejected"));
}
}
}
let res = HandleResponse {
messages: msgs,
log: vec![log("action", "execute"), log("owner", env.message.sender)],
data: None,
};
Ok(res)
}
}
pub fn check_staking_permissions(
staking_msg: &StakingMsg,
permissions: Permissions,
) -> Result<bool, PermissionErr> {
match staking_msg {
StakingMsg::Delegate { .. } => {
if !permissions.delegate {
return Err(PermissionErr::Delegate {});
}
}
StakingMsg::Undelegate { .. } => {
if !permissions.undelegate {
return Err(PermissionErr::Undelegate {});
}
}
StakingMsg::Redelegate { .. } => {
if !permissions.redelegate {
return Err(PermissionErr::Redelegate {});
}
}
StakingMsg::Withdraw { .. } => {
if !permissions.withdraw {
return Err(PermissionErr::Withdraw {});
}
}
}
Ok(true)
}
pub fn handle_increase_allowance<S: Storage, A: Api, Q: Querier, T>(
deps: &mut Extern<S, A, Q>,
env: Env,
spender: HumanAddr,
amount: Coin,
expires: Option<Expiration>,
) -> StdResult<HandleResponse<T>>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
let cfg = admin_list_read(&deps.storage).load()?;
let spender_raw = &deps.api.canonical_address(&spender)?;
let owner_raw = &deps.api.canonical_address(&env.message.sender)?;
if !cfg.is_admin(&owner_raw) {
return Err(StdError::unauthorized());
}
if spender_raw == owner_raw {
return Err(StdError::generic_err("Cannot set allowance to own account"));
}
allowances(&mut deps.storage).update(spender_raw.as_slice(), |allow| {
let mut allowance = allow.unwrap_or_default();
if let Some(exp) = expires {
allowance.expires = exp;
}
allowance.balance.add_assign(amount.clone());
Ok(allowance)
})?;
let res = HandleResponse {
messages: vec![],
log: vec![
log("action", "increase_allowance"),
log("owner", env.message.sender),
log("spender", spender),
log("denomination", amount.denom),
log("amount", amount.amount),
],
data: None,
};
Ok(res)
}
pub fn handle_decrease_allowance<S: Storage, A: Api, Q: Querier, T>(
deps: &mut Extern<S, A, Q>,
env: Env,
spender: HumanAddr,
amount: Coin,
expires: Option<Expiration>,
) -> StdResult<HandleResponse<T>>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
let cfg = admin_list_read(&deps.storage).load()?;
let spender_raw = &deps.api.canonical_address(&spender)?;
let owner_raw = &deps.api.canonical_address(&env.message.sender)?;
if !cfg.is_admin(&owner_raw) {
return Err(StdError::unauthorized());
}
if spender_raw == owner_raw {
return Err(StdError::generic_err("Cannot set allowance to own account"));
}
let allowance = allowances(&mut deps.storage).update(spender_raw.as_slice(), |allow| {
let mut allowance =
allow.ok_or_else(|| StdError::not_found("No allowance for this account"))?;
if let Some(exp) = expires {
allowance.expires = exp;
}
allowance.balance = allowance.balance.sub_saturating(amount.clone())?; Ok(allowance)
})?;
if allowance.balance.is_empty() {
allowances(&mut deps.storage).remove(spender_raw.as_slice());
}
let res = HandleResponse {
messages: vec![],
log: vec![
log("action", "decrease_allowance"),
log("owner", deps.api.human_address(owner_raw)?),
log("spender", spender),
log("denomination", amount.denom),
log("amount", amount.amount),
],
data: None,
};
Ok(res)
}
pub fn handle_set_permissions<S: Storage, A: Api, Q: Querier, T>(
deps: &mut Extern<S, A, Q>,
env: Env,
spender: HumanAddr,
perm: Permissions,
) -> StdResult<HandleResponse<T>>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
let cfg = admin_list_read(&deps.storage).load()?;
let spender_raw = &deps.api.canonical_address(&spender)?;
let owner_raw = &deps.api.canonical_address(&env.message.sender)?;
if !cfg.is_admin(&owner_raw) {
return Err(StdError::unauthorized());
}
if spender_raw == owner_raw {
return Err(StdError::generic_err(
"Cannot set permission to own account",
));
}
permissions(&mut deps.storage).save(spender_raw.as_slice(), &perm)?;
let res = HandleResponse {
messages: vec![],
log: vec![
log("action", "set_permissions"),
log("owner", deps.api.human_address(owner_raw)?),
log("spender", spender),
log("permissions", perm),
],
data: None,
};
Ok(res)
}
pub fn query<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
msg: QueryMsg,
) -> StdResult<Binary> {
match msg {
QueryMsg::AdminList {} => to_binary(&query_admin_list(deps)?),
QueryMsg::Allowance { spender } => to_binary(&query_allowance(deps, spender)?),
QueryMsg::Permissions { spender } => to_binary(&query_permissions(deps, spender)?),
QueryMsg::CanSend { sender, msg } => to_binary(&query_can_send(deps, sender, msg)?),
QueryMsg::AllAllowances { start_after, limit } => {
to_binary(&query_all_allowances(deps, start_after, limit)?)
}
QueryMsg::AllPermissions { start_after, limit } => {
to_binary(&query_all_permissions(deps, start_after, limit)?)
}
}
}
pub fn query_allowance<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
spender: HumanAddr,
) -> StdResult<Allowance> {
let subkey = deps.api.canonical_address(&spender)?;
let allow = allowances_read(&deps.storage)
.may_load(subkey.as_slice())?
.unwrap_or_default();
Ok(allow)
}
pub fn query_permissions<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
spender: HumanAddr,
) -> StdResult<Permissions> {
let subkey = deps.api.canonical_address(&spender)?;
let permissions = permissions_read(&deps.storage)
.may_load(subkey.as_slice())?
.unwrap_or_default();
Ok(permissions)
}
fn query_can_send<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
sender: HumanAddr,
msg: CosmosMsg,
) -> StdResult<CanSendResponse> {
Ok(CanSendResponse {
can_send: can_send(deps, sender, msg)?,
})
}
fn can_send<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
sender: HumanAddr,
msg: CosmosMsg,
) -> StdResult<bool> {
let owner_raw = deps.api.canonical_address(&sender)?;
let cfg = admin_list_read(&deps.storage).load()?;
if cfg.is_admin(&owner_raw) {
return Ok(true);
}
match msg {
CosmosMsg::Bank(BankMsg::Send { amount, .. }) => {
let allowance = allowances_read(&deps.storage).may_load(owner_raw.as_slice())?;
match allowance {
Some(allow) => Ok(allow.balance.sub(amount).is_ok()),
None => Ok(false),
}
}
CosmosMsg::Staking(staking_msg) => {
let perm_opt = permissions_read(&deps.storage).may_load(owner_raw.as_slice())?;
match perm_opt {
Some(permission) => Ok(check_staking_permissions(&staking_msg, permission).is_ok()),
None => Ok(false),
}
}
_ => Ok(false),
}
}
const MAX_LIMIT: u32 = 30;
const DEFAULT_LIMIT: u32 = 10;
fn calc_limit(request: Option<u32>) -> usize {
request.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize
}
fn calc_range_start(start_after: Option<HumanAddr>) -> Option<Vec<u8>> {
match start_after {
Some(human) => {
let mut v = Vec::from(human.0);
v.push(1);
Some(v)
}
None => None,
}
}
pub fn query_all_allowances<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
start_after: Option<HumanAddr>,
limit: Option<u32>,
) -> StdResult<AllAllowancesResponse> {
let limit = calc_limit(limit);
let range_start = calc_range_start(start_after);
let api = &deps.api;
let res: StdResult<Vec<AllowanceInfo>> = allowances_read(&deps.storage)
.range(range_start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|item| {
item.and_then(|(k, allow)| {
Ok(AllowanceInfo {
spender: api.human_address(&CanonicalAddr::from(k))?,
balance: allow.balance,
expires: allow.expires,
})
})
})
.collect();
Ok(AllAllowancesResponse { allowances: res? })
}
pub fn query_all_permissions<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
start_after: Option<HumanAddr>,
limit: Option<u32>,
) -> StdResult<AllPermissionsResponse> {
let limit = calc_limit(limit);
let range_start = calc_range_start(start_after);
let api = &deps.api;
let res: StdResult<Vec<PermissionsInfo>> = permissions_read(&deps.storage)
.range(range_start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|item| {
item.and_then(|(k, perm)| {
Ok(PermissionsInfo {
spender: api.human_address(&CanonicalAddr::from(k))?,
permissions: perm,
})
})
})
.collect();
Ok(AllPermissionsResponse { permissions: res? })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::balance::Balance;
use crate::state::Permissions;
use cosmwasm_std::testing::{mock_dependencies, mock_env, MOCK_CONTRACT_ADDR};
use cosmwasm_std::{coin, coins, StakingMsg};
use cw1_whitelist::msg::AdminListResponse;
use cw2::{get_contract_version, ContractVersion};
fn setup_test_case<S: Storage, A: Api, Q: Querier>(
mut deps: &mut Extern<S, A, Q>,
env: &Env,
admins: &[HumanAddr],
spenders: &[HumanAddr],
allowances: &[Coin],
expirations: &[Expiration],
) {
let init_msg = InitMsg {
admins: admins.to_vec(),
mutable: true,
};
init(deps, env.clone(), init_msg).unwrap();
for (spender, expiration) in spenders.iter().zip(expirations) {
for amount in allowances {
let msg = HandleMsg::IncreaseAllowance {
spender: spender.clone(),
amount: amount.clone(),
expires: Some(expiration.clone()),
};
handle(&mut deps, env.clone(), msg).unwrap();
}
}
}
#[test]
fn get_contract_version_works() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone(), HumanAddr::from("admin0002")];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let initial_spenders = vec![spender1.clone(), spender2.clone()];
let denom1 = "token1";
let amount1 = 1111;
let allow1 = coin(amount1, denom1);
let initial_allowances = vec![allow1.clone()];
let expires_never = Expiration::Never {};
let initial_expirations = vec![expires_never.clone(), expires_never.clone()];
let env = mock_env(owner, &[]);
setup_test_case(
&mut deps,
&env,
&admins,
&initial_spenders,
&initial_allowances,
&initial_expirations,
);
assert_eq!(
ContractVersion {
contract: CONTRACT_NAME.to_string(),
version: CONTRACT_VERSION.to_string(),
},
get_contract_version(&deps.storage).unwrap()
)
}
#[test]
fn query_allowance_works() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone(), HumanAddr::from("admin0002")];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let spender3 = HumanAddr::from("spender0003");
let initial_spenders = vec![spender1.clone(), spender2.clone()];
let denom1 = "token1";
let amount1 = 1111;
let allow1 = coin(amount1, denom1);
let initial_allowances = vec![allow1.clone()];
let expires_never = Expiration::Never {};
let initial_expirations = vec![expires_never.clone(), expires_never.clone()];
let env = mock_env(owner, &[]);
setup_test_case(
&mut deps,
&env,
&admins,
&initial_spenders,
&initial_allowances,
&initial_expirations,
);
let allowance = query_allowance(&deps, spender1.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow1.clone()]),
expires: expires_never.clone(),
}
);
let allowance = query_allowance(&deps, spender2.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow1.clone()]),
expires: expires_never.clone(),
}
);
let allowance = query_allowance(&deps, spender3.clone()).unwrap();
assert_eq!(allowance, Allowance::default(),);
}
#[test]
fn query_all_allowances_works() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone(), HumanAddr::from("admin0002")];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let spender3 = HumanAddr::from("spender0003");
let initial_spenders = vec![spender1.clone(), spender2.clone(), spender3.clone()];
let initial_allowances = coins(1234, "mytoken");
let expires_later = Expiration::AtHeight(12345);
let initial_expirations = vec![
Expiration::Never {},
Expiration::Never {},
expires_later.clone(),
];
let env = mock_env(owner, &[]);
setup_test_case(
&mut deps,
&env,
&admins,
&initial_spenders,
&initial_allowances,
&initial_expirations,
);
let allowances = query_all_allowances(&deps, None, Some(2))
.unwrap()
.allowances;
assert_eq!(2, allowances.len());
assert_eq!(
allowances[0],
AllowanceInfo {
spender: spender1,
balance: Balance(initial_allowances.clone()),
expires: Expiration::Never {},
}
);
assert_eq!(
allowances[1],
AllowanceInfo {
spender: spender2.clone(),
balance: Balance(initial_allowances.clone()),
expires: Expiration::Never {},
}
);
let allowances = query_all_allowances(&deps, Some(spender2), Some(2))
.unwrap()
.allowances;
assert_eq!(1, allowances.len());
assert_eq!(
allowances[0],
AllowanceInfo {
spender: spender3,
balance: Balance(initial_allowances.clone()),
expires: expires_later,
}
);
}
#[test]
fn query_permissions_works() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone()];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let spender3 = HumanAddr::from("spender0003");
let god_mode = Permissions {
delegate: true,
redelegate: true,
undelegate: true,
withdraw: true,
};
let env = mock_env(owner.clone(), &[]);
let init_msg = InitMsg {
admins: admins.clone(),
mutable: true,
};
init(&mut deps, env.clone(), init_msg).unwrap();
let setup_perm_msg1 = HandleMsg::SetPermissions {
spender: spender1.clone(),
permissions: god_mode,
};
handle(&mut deps, env.clone(), setup_perm_msg1).unwrap();
let setup_perm_msg2 = HandleMsg::SetPermissions {
spender: spender2.clone(),
permissions: Default::default(),
};
handle(&mut deps, env.clone(), setup_perm_msg2).unwrap();
let permissions = query_permissions(&deps, spender1.clone()).unwrap();
assert_eq!(permissions, god_mode);
let permissions = query_permissions(&deps, spender2.clone()).unwrap();
assert_eq!(
permissions,
Permissions {
delegate: false,
redelegate: false,
undelegate: false,
withdraw: false
},
);
let permissions = query_permissions(&deps, spender3.clone()).unwrap();
assert_eq!(
permissions,
Permissions {
delegate: false,
redelegate: false,
undelegate: false,
withdraw: false
},
);
}
#[test]
fn query_all_permissions_works() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone(), HumanAddr::from("admin0002")];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let spender3 = HumanAddr::from("spender0003");
let god_mode = Permissions {
delegate: true,
redelegate: true,
undelegate: true,
withdraw: true,
};
let noob_mode = Permissions {
delegate: false,
redelegate: false,
undelegate: false,
withdraw: false,
};
let env = mock_env(owner, &[]);
let init_msg = InitMsg {
admins: admins.clone(),
mutable: true,
};
init(&mut deps, env.clone(), init_msg).unwrap();
let setup_perm_msg1 = HandleMsg::SetPermissions {
spender: spender1.clone(),
permissions: god_mode,
};
handle(&mut deps, env.clone(), setup_perm_msg1).unwrap();
let setup_perm_msg2 = HandleMsg::SetPermissions {
spender: spender2.clone(),
permissions: noob_mode,
};
handle(&mut deps, env.clone(), setup_perm_msg2).unwrap();
let setup_perm_msg3 = HandleMsg::SetPermissions {
spender: spender3.clone(),
permissions: noob_mode,
};
handle(&mut deps, env.clone(), setup_perm_msg3).unwrap();
let permissions = query_all_permissions(&deps, None, Some(2))
.unwrap()
.permissions;
assert_eq!(2, permissions.len());
assert_eq!(
permissions[0],
PermissionsInfo {
spender: spender1,
permissions: god_mode,
}
);
assert_eq!(
permissions[1],
PermissionsInfo {
spender: spender2.clone(),
permissions: noob_mode,
}
);
let permissions = query_all_permissions(&deps, Some(spender2), Some(2))
.unwrap()
.permissions;
assert_eq!(1, permissions.len());
assert_eq!(
permissions[0],
PermissionsInfo {
spender: spender3,
permissions: noob_mode,
}
);
}
#[test]
fn update_admins_and_query() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admin2 = HumanAddr::from("admin0002");
let admin3 = HumanAddr::from("admin0003");
let initial_admins = vec![owner.clone(), admin2.clone()];
let env = mock_env(owner.clone(), &[]);
setup_test_case(&mut deps, &env, &initial_admins, &vec![], &vec![], &vec![]);
let config = query_admin_list(&deps).unwrap();
assert_eq!(
config,
AdminListResponse {
admins: initial_admins.clone(),
mutable: true,
}
);
let new_admins = vec![owner.clone(), admin2.clone(), admin3.clone()];
let msg = HandleMsg::UpdateAdmins {
admins: new_admins.clone(),
};
handle(&mut deps, env.clone(), msg).unwrap();
let config = query_admin_list(&deps).unwrap();
println!("config: {:#?}", config);
assert_eq!(
config,
AdminListResponse {
admins: new_admins,
mutable: true,
}
);
let msg = HandleMsg::UpdateAdmins {
admins: vec![admin3.clone()],
};
handle(&mut deps, env.clone(), msg).unwrap();
let config = query_admin_list(&deps).unwrap();
println!("config: {:#?}", config);
assert_eq!(
config,
AdminListResponse {
admins: vec![admin3.clone()],
mutable: true,
}
);
let msg = HandleMsg::UpdateAdmins {
admins: vec![admin3.clone(), owner.clone()],
};
let res = handle(&mut deps, env.clone(), msg);
assert!(res.is_err());
let env = mock_env(admin3.clone(), &[]);
let msg = HandleMsg::UpdateAdmins {
admins: vec![admin3.clone(), owner.clone()],
};
handle(&mut deps, env.clone(), msg).unwrap();
let config = query_admin_list(&deps).unwrap();
println!("config: {:#?}", config);
assert_eq!(
config,
AdminListResponse {
admins: vec![admin3, owner],
mutable: true,
}
);
}
#[test]
fn increase_allowances() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone(), HumanAddr::from("admin0002")];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let spender3 = HumanAddr::from("spender0003");
let spender4 = HumanAddr::from("spender0004");
let initial_spenders = vec![spender1.clone(), spender2.clone()];
let denom1 = "token1";
let denom2 = "token2";
let denom3 = "token3";
let amount1 = 1111;
let amount2 = 2222;
let amount3 = 3333;
let allow1 = coin(amount1, denom1);
let allow2 = coin(amount2, denom2);
let allow3 = coin(amount3, denom3);
let initial_allowances = vec![allow1.clone(), allow2.clone()];
let expires_height = Expiration::AtHeight(5432);
let expires_never = Expiration::Never {};
let expires_time = Expiration::AtTime(1234567890);
let initial_expirations = vec![expires_height.clone(), expires_never.clone()];
let env = mock_env(owner, &[]);
setup_test_case(
&mut deps,
&env,
&admins,
&initial_spenders,
&initial_allowances,
&initial_expirations,
);
let msg = HandleMsg::IncreaseAllowance {
spender: spender1.clone(),
amount: allow1.clone(),
expires: None,
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender1.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![coin(amount1 * 2, &allow1.denom), allow2.clone()]),
expires: expires_height.clone(),
}
);
let msg = HandleMsg::IncreaseAllowance {
spender: spender2.clone(),
amount: allow3.clone(),
expires: Some(expires_height.clone()),
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender2.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow1.clone(), allow2.clone(), allow3.clone()]),
expires: expires_height.clone(),
}
);
let msg = HandleMsg::IncreaseAllowance {
spender: spender3.clone(),
amount: allow1.clone(),
expires: None,
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender3.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow1.clone()]),
expires: expires_never.clone(),
}
);
let msg = HandleMsg::IncreaseAllowance {
spender: spender4.clone(),
amount: allow2.clone(),
expires: Some(expires_time.clone()),
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender4.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow2.clone()]),
expires: expires_time,
}
);
}
#[test]
fn decrease_allowances() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone(), HumanAddr::from("admin0002")];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let initial_spenders = vec![spender1.clone(), spender2.clone()];
let denom1 = "token1";
let denom2 = "token2";
let denom3 = "token3";
let amount1 = 1111;
let amount2 = 2222;
let amount3 = 3333;
let allow1 = coin(amount1, denom1);
let allow2 = coin(amount2, denom2);
let allow3 = coin(amount3, denom3);
let initial_allowances = vec![coin(amount1, denom1), coin(amount2, denom2)];
let expires_height = Expiration::AtHeight(5432);
let expires_never = Expiration::Never {};
let initial_expirations = vec![expires_height.clone(), expires_never.clone()];
let env = mock_env(owner, &[]);
setup_test_case(
&mut deps,
&env,
&admins,
&initial_spenders,
&initial_allowances,
&initial_expirations,
);
let msg = HandleMsg::DecreaseAllowance {
spender: spender1.clone(),
amount: allow3.clone(),
expires: None,
};
let res = handle(&mut deps, env.clone(), msg);
assert!(res.is_err());
let allowance = query_allowance(&deps, spender1.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow1.clone(), allow2.clone()]),
expires: expires_height.clone(),
}
);
let msg = HandleMsg::DecreaseAllowance {
spender: spender2.clone(),
amount: allow2.clone(),
expires: None,
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender2.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow1.clone()]),
expires: expires_never.clone(),
}
);
let msg = HandleMsg::DecreaseAllowance {
spender: spender1.clone(),
amount: coin(amount1 / 2, denom1),
expires: None,
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender1.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![
coin(amount1 / 2 + (amount1 & 1), denom1),
allow2.clone()
]),
expires: expires_height.clone(),
}
);
let msg = HandleMsg::DecreaseAllowance {
spender: spender2.clone(),
amount: allow1.clone(),
expires: None,
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender2.clone()).unwrap();
assert_eq!(allowance, Allowance::default());
let msg = HandleMsg::DecreaseAllowance {
spender: spender2.clone(),
amount: allow1.clone(),
expires: None,
};
let res = handle(&mut deps, env.clone(), msg);
assert!(res.is_err());
let msg = HandleMsg::DecreaseAllowance {
spender: spender1.clone(),
amount: coin(amount1 * 10, denom1),
expires: None,
};
handle(&mut deps, env.clone(), msg).unwrap();
let allowance = query_allowance(&deps, spender1.clone()).unwrap();
assert_eq!(
allowance,
Allowance {
balance: Balance(vec![allow2]),
expires: expires_height.clone(),
}
);
}
#[test]
fn execute_checks() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone(), HumanAddr::from("admin0002")];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let initial_spenders = vec![spender1.clone()];
let denom1 = "token1";
let amount1 = 1111;
let allow1 = coin(amount1, denom1);
let initial_allowances = vec![allow1];
let expires_never = Expiration::Never {};
let initial_expirations = vec![expires_never.clone()];
let env = mock_env(owner.clone(), &[]);
setup_test_case(
&mut deps,
&env,
&admins,
&initial_spenders,
&initial_allowances,
&initial_expirations,
);
let msgs = vec![BankMsg::Send {
from_address: HumanAddr::from(MOCK_CONTRACT_ADDR),
to_address: spender2.clone(),
amount: coins(1000, "token1"),
}
.into()];
let handle_msg = HandleMsg::Execute { msgs: msgs.clone() };
let env = mock_env(&spender2, &[]);
let res = handle(&mut deps, env, handle_msg.clone());
match res.unwrap_err() {
StdError::NotFound { .. } => {}
e => panic!("unexpected error: {}", e),
}
let env = mock_env(&spender1, &[]);
let res = handle(&mut deps, env.clone(), handle_msg.clone()).unwrap();
assert_eq!(res.messages, msgs);
assert_eq!(
res.log,
vec![log("action", "execute"), log("owner", spender1.clone())]
);
let res = handle(&mut deps, env, handle_msg.clone());
match res.unwrap_err() {
StdError::Underflow { .. } => {}
e => panic!("unexpected error: {}", e),
}
let env = mock_env(&owner.clone(), &[]);
let res = handle(&mut deps, env.clone(), handle_msg.clone()).unwrap();
assert_eq!(res.messages, msgs);
assert_eq!(
res.log,
vec![log("action", "execute"), log("owner", owner.clone())]
);
let other_msgs = vec![CosmosMsg::Custom(Empty {})];
let handle_msg = HandleMsg::Execute {
msgs: other_msgs.clone(),
};
let env = mock_env(&owner, &[]);
let res = handle(&mut deps, env, handle_msg.clone()).unwrap();
assert_eq!(res.messages, other_msgs);
assert_eq!(res.log, vec![log("action", "execute"), log("owner", owner)]);
let env = mock_env(&spender1, &[]);
let res = handle(&mut deps, env, handle_msg.clone());
match res.unwrap_err() {
StdError::GenericErr { msg, .. } => assert_eq!(&msg, "Message type rejected"),
e => panic!("unexpected error: {}", e),
}
}
#[test]
fn staking_permission_checks() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone()];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let denom = "token1";
let amount = 10000;
let coin1 = coin(amount, denom);
let god_mode = Permissions {
delegate: true,
redelegate: true,
undelegate: true,
withdraw: true,
};
let env = mock_env(owner.clone(), &[]);
let init_msg = InitMsg {
admins: admins.clone(),
mutable: true,
};
init(&mut deps, env.clone(), init_msg).unwrap();
let setup_perm_msg1 = HandleMsg::SetPermissions {
spender: spender1.clone(),
permissions: god_mode,
};
handle(&mut deps, env.clone(), setup_perm_msg1).unwrap();
let setup_perm_msg2 = HandleMsg::SetPermissions {
spender: spender2.clone(),
permissions: Default::default(),
};
handle(&mut deps, env.clone(), setup_perm_msg2).unwrap();
let msg_delegate = vec![StakingMsg::Delegate {
validator: HumanAddr::from("validator1"),
amount: coin1.clone(),
}
.into()];
let msg_redelegate = vec![StakingMsg::Redelegate {
src_validator: HumanAddr::from("validator1"),
dst_validator: HumanAddr::from("validator2"),
amount: coin1.clone(),
}
.into()];
let msg_undelegate = vec![StakingMsg::Undelegate {
validator: HumanAddr::from("validator1"),
amount: coin1.clone(),
}
.into()];
let msg_withdraw = vec![StakingMsg::Withdraw {
validator: HumanAddr::from("validator1"),
recipient: None,
}
.into()];
let msgs = vec![
msg_delegate.clone(),
msg_redelegate.clone(),
msg_undelegate.clone(),
msg_withdraw.clone(),
];
for msg in &msgs {
let env = mock_env(&spender1, &[]);
let res = handle(&mut deps, env, HandleMsg::Execute { msgs: msg.clone() });
assert!(res.is_ok())
}
for msg in &msgs {
let env = mock_env(&spender2, &[]);
let res = handle(&mut deps, env, HandleMsg::Execute { msgs: msg.clone() });
assert!(res.is_err())
}
let spender3 = HumanAddr::from("spender0003");
let setup_perm_msg3 = HandleMsg::SetPermissions {
spender: spender3.clone(),
permissions: Permissions {
delegate: false,
redelegate: true,
undelegate: true,
withdraw: false,
},
};
handle(&mut deps, env.clone(), setup_perm_msg3).unwrap();
let env = mock_env(&spender3, &[]);
let res = handle(
&mut deps,
env.clone(),
HandleMsg::Execute {
msgs: msg_delegate.clone(),
},
);
assert!(res.is_err());
let res = handle(
&mut deps,
env.clone(),
HandleMsg::Execute {
msgs: msg_redelegate.clone(),
},
);
assert!(res.is_ok());
let res = handle(
&mut deps,
env.clone(),
HandleMsg::Execute {
msgs: msg_undelegate.clone(),
},
);
assert!(res.is_ok());
let res = handle(
&mut deps,
env.clone(),
HandleMsg::Execute {
msgs: msg_withdraw.clone(),
},
);
assert!(res.is_err())
}
#[test]
fn permissions_allowances_independent() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin0001");
let admins = vec![owner.clone()];
let spender1 = HumanAddr::from("spender0001");
let spender2 = HumanAddr::from("spender0002");
let denom = "token1";
let amount = 10000;
let coin = coin(amount, denom);
let allow = Allowance {
balance: Balance(vec![coin.clone()]),
expires: Expiration::Never {},
};
let perm = Permissions {
delegate: true,
redelegate: false,
undelegate: false,
withdraw: true,
};
let env = mock_env(owner.clone(), &[]);
let init_msg = InitMsg {
admins: admins.clone(),
mutable: true,
};
init(&mut deps, env.clone(), init_msg).unwrap();
let setup_perm_msg = HandleMsg::SetPermissions {
spender: spender1.clone(),
permissions: perm,
};
handle(&mut deps, env.clone(), setup_perm_msg).unwrap();
let setup_allowance_msg = HandleMsg::IncreaseAllowance {
spender: spender1.clone(),
amount: coin.clone(),
expires: None,
};
handle(&mut deps, env.clone(), setup_allowance_msg).unwrap();
let res_perm = query_permissions(&deps, spender1.clone()).unwrap();
assert_eq!(perm, res_perm);
let res_allow = query_allowance(&deps, spender1.clone()).unwrap();
assert_eq!(allow, res_allow);
let setup_allowance_msg = HandleMsg::IncreaseAllowance {
spender: spender2.clone(),
amount: coin.clone(),
expires: None,
};
handle(&mut deps, env.clone(), setup_allowance_msg).unwrap();
let setup_perm_msg = HandleMsg::SetPermissions {
spender: spender2.clone(),
permissions: perm,
};
handle(&mut deps, env.clone(), setup_perm_msg).unwrap();
let res_perm = query_permissions(&deps, spender2.clone()).unwrap();
assert_eq!(perm, res_perm);
let res_allow = query_allowance(&deps, spender2.clone()).unwrap();
assert_eq!(allow, res_allow);
}
#[test]
fn can_send_query_works() {
let mut deps = mock_dependencies(20, &[]);
let owner = HumanAddr::from("admin007");
let spender = HumanAddr::from("spender808");
let anyone = HumanAddr::from("anyone");
let env = mock_env(owner.clone(), &[]);
setup_test_case(
&mut deps,
&env,
&[owner.clone()],
&[spender.clone()],
&coins(55000, "ushell"),
&[Expiration::Never {}],
);
let perm = Permissions {
delegate: true,
redelegate: true,
undelegate: false,
withdraw: false,
};
let spender_raw = &deps.api.canonical_address(&spender).unwrap();
let _ = permissions(&mut deps.storage).save(spender_raw.as_slice(), &perm);
let send_msg = CosmosMsg::Bank(BankMsg::Send {
from_address: MOCK_CONTRACT_ADDR.into(),
to_address: anyone.clone(),
amount: coins(12345, "ushell"),
});
let send_msg_large = CosmosMsg::Bank(BankMsg::Send {
from_address: MOCK_CONTRACT_ADDR.into(),
to_address: anyone.clone(),
amount: coins(1234567, "ushell"),
});
let staking_delegate_msg = CosmosMsg::Staking(StakingMsg::Delegate {
validator: anyone.clone(),
amount: coin(70000, "ureef"),
});
let staking_withdraw_msg = CosmosMsg::Staking(StakingMsg::Withdraw {
validator: anyone.clone(),
recipient: None,
});
let res = query_can_send(&deps, owner.clone(), send_msg.clone()).unwrap();
assert_eq!(res.can_send, true);
let res = query_can_send(&deps, owner.clone(), send_msg_large.clone()).unwrap();
assert_eq!(res.can_send, true);
let res = query_can_send(&deps, owner.clone(), staking_delegate_msg.clone()).unwrap();
assert_eq!(res.can_send, true);
let res = query_can_send(&deps, spender.clone(), send_msg.clone()).unwrap();
assert_eq!(res.can_send, true);
let res = query_can_send(&deps, spender.clone(), send_msg_large.clone()).unwrap();
assert_eq!(res.can_send, false);
let res = query_can_send(&deps, spender.clone(), staking_delegate_msg.clone()).unwrap();
assert_eq!(res.can_send, true);
let res = query_can_send(&deps, spender.clone(), staking_withdraw_msg.clone()).unwrap();
assert_eq!(res.can_send, false);
let res = query_can_send(&deps, anyone.clone(), send_msg.clone()).unwrap();
assert_eq!(res.can_send, false);
let res = query_can_send(&deps, anyone.clone(), send_msg_large.clone()).unwrap();
assert_eq!(res.can_send, false);
let res = query_can_send(&deps, anyone.clone(), staking_delegate_msg.clone()).unwrap();
assert_eq!(res.can_send, false);
let res = query_can_send(&deps, anyone.clone(), staking_withdraw_msg.clone()).unwrap();
assert_eq!(res.can_send, false);
}
}