use std::ops::{Deref, Mul};
use std::str::FromStr;
use cosmwasm_std::{
coins, ensure, Addr, BankMsg, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Order, StdResult,
Storage, Uint128,
};
use cw_utils::must_pay;
use crate::state::{
COMMISSION_CHANGE, DELEGATES, DENOM, OWNER, STAKE, TOTAL_COLDKEY_STAKE, TOTAL_HOTKEY_STAKE,
TOTAL_ISSUANCE, TOTAL_STAKE,
};
use crate::utils::{exceeds_tx_rate_limit, get_default_take, get_last_tx_block, set_last_tx_block};
use crate::ContractError;
use cyber_std::Response;
pub fn do_become_delegate(
deps: DepsMut,
env: Env,
info: MessageInfo,
hotkey_address: String,
) -> Result<Response, ContractError> {
let take = get_default_take(deps.storage);
let coldkey = info.sender;
let hotkey = deps.api.addr_validate(&hotkey_address)?;
deps.api.debug(&format!(
"๐ do_become_delegate ( coldkey:{:?} hotkey:{:?}, take:{:?} )",
coldkey, hotkey, take
));
ensure!(
hotkey_account_exists(deps.storage, &hotkey),
ContractError::NotRegistered {}
);
ensure!(
coldkey_owns_hotkey(deps.storage, &coldkey, &hotkey),
ContractError::NonAssociatedColdKey {}
);
ensure!(
!hotkey_is_delegate(deps.storage, &hotkey),
ContractError::AlreadyDelegate {}
);
ensure!(
!exceeds_tx_rate_limit(
deps.storage,
get_last_tx_block(deps.storage, &coldkey),
env.block.height
),
ContractError::TxRateLimitExceeded {}
);
delegate_hotkey(deps.storage, &hotkey, take);
set_last_tx_block(deps.storage, &coldkey, env.block.height);
deps.api.debug(&format!(
"๐ DelegateAdded( coldkey:{:?}, hotkey:{:?}, take:{:?} )",
coldkey,
hotkey.clone(),
take
));
Ok(Response::default()
.add_attribute("action", "delegate_added")
.add_attribute("hotkey", hotkey)
.add_attribute("take", format!("{}", take)))
}
pub fn do_add_stake(
deps: DepsMut,
env: Env,
info: MessageInfo,
hotkey_address: String,
) -> Result<Response, ContractError> {
let coldkey = info.clone().sender;
let hotkey = deps.api.addr_validate(&hotkey_address)?;
let denom = DENOM.load(deps.storage)?;
let stake_to_be_added =
must_pay(&info, &denom).map_err(|_| ContractError::CouldNotConvertToBalance {})?;
deps.api.debug(&format!(
"๐ do_add_stake ( coldkey:{:?}, hotkey:{:?}, stake_to_be_added:{:?} )",
coldkey, hotkey, stake_to_be_added
));
ensure!(
hotkey_account_exists(deps.storage, &hotkey),
ContractError::NotRegistered {}
);
ensure!(
hotkey_is_delegate(deps.storage, &hotkey)
|| coldkey_owns_hotkey(deps.storage, &coldkey, &hotkey),
ContractError::NonAssociatedColdKey {}
);
ensure!(
!exceeds_tx_rate_limit(
deps.storage,
get_last_tx_block(deps.storage, &coldkey),
env.block.height
),
ContractError::TxRateLimitExceeded {}
);
let stake_amount = u64::try_from(stake_to_be_added.u128())
.map_err(|_| ContractError::CouldNotConvertToBalance {})?;
increase_stake_on_coldkey_hotkey_account(deps.storage, &coldkey, &hotkey, stake_amount);
deps.api.debug(&format!(
"๐ StakeAdded ( hotkey:{:?}, stake_to_be_added:{:?} )",
hotkey.clone(),
stake_to_be_added
));
Ok(Response::default()
.add_attribute("action", "stake_added")
.add_attribute("hotkey", hotkey)
.add_attribute("take", format!("{:?}", stake_to_be_added)))
}
pub fn do_remove_stake(
deps: DepsMut,
env: Env,
info: MessageInfo,
hotkey_address: String,
stake_to_be_removed: u64,
) -> Result<Response, ContractError> {
let coldkey = info.clone().sender;
let hotkey = deps.api.addr_validate(&hotkey_address)?;
deps.api.debug(&format!(
"๐ do_remove_stake ( coldkey:{:?}, hotkey:{:?}, stake_to_be_removed:{:?} )",
coldkey, hotkey, stake_to_be_removed
));
ensure!(
hotkey_account_exists(deps.storage, &hotkey),
ContractError::NotRegistered {}
);
ensure!(
hotkey_is_delegate(deps.storage, &hotkey)
|| coldkey_owns_hotkey(deps.storage, &coldkey, &hotkey),
ContractError::NonAssociatedColdKey {}
);
ensure!(
stake_to_be_removed > 0,
ContractError::NotEnoughStaketoWithdraw {}
);
ensure!(
has_enough_stake(deps.storage, &coldkey, &hotkey, stake_to_be_removed),
ContractError::NotEnoughStaketoWithdraw {}
);
ensure!(
!exceeds_tx_rate_limit(
deps.storage,
get_last_tx_block(deps.storage, &coldkey),
env.block.height
),
ContractError::TxRateLimitExceeded {}
);
decrease_stake_on_coldkey_hotkey_account(deps.storage, &coldkey, &hotkey, stake_to_be_removed)?;
let denom = DENOM.load(deps.storage)?;
let msg = CosmosMsg::Bank(BankMsg::Send {
to_address: info.sender.to_string(),
amount: coins(Uint128::from(stake_to_be_removed).u128(), denom),
});
deps.api.debug(&format!(
"๐ StakeRemoved ( hotkey:{:?}, stake_to_be_removed:{:?} )",
hotkey, stake_to_be_removed
));
Ok(Response::default()
.add_message(msg)
.add_attribute("action", "stake_removed")
.add_attribute("hotkey", hotkey.clone())
.add_attribute("stake_to_be_removed", format!("{}", stake_to_be_removed)))
}
pub fn do_set_delegate_commission(
deps: DepsMut,
env: Env,
info: MessageInfo,
hotkey_address: String,
new_commission: String,
) -> Result<Response, ContractError> {
let commission_change = COMMISSION_CHANGE.load(deps.storage)?;
ensure!(
commission_change,
ContractError::CommissionChangeDisabled {}
);
let commission =
Decimal::from_str(&new_commission).map_err(|_| ContractError::InvalidCommission {})?;
ensure!(
commission > Decimal::zero() && commission <= Decimal::one(),
ContractError::InvalidCommission {}
);
let coldkey = info.sender;
let hotkey = deps.api.addr_validate(&hotkey_address)?;
deps.api.debug(&format!(
"๐ do_set_deletate_commission ( coldkey:{:?} hotkey:{:?}, commission:{:?} )",
coldkey, hotkey, new_commission
));
ensure!(
hotkey_account_exists(deps.storage, &hotkey),
ContractError::NotRegistered {}
);
ensure!(
coldkey_owns_hotkey(deps.storage, &coldkey, &hotkey),
ContractError::NonAssociatedColdKey {}
);
ensure!(
!exceeds_tx_rate_limit(
deps.storage,
get_last_tx_block(deps.storage, &coldkey),
env.block.height
),
ContractError::TxRateLimitExceeded {}
);
let take = Decimal::new(Uint128::new(65536u128))
.mul(commission)
.to_uint_floor()
.u128();
delegate_hotkey(deps.storage, &hotkey, take as u16);
set_last_tx_block(deps.storage, &coldkey, env.block.height);
deps.api.debug(&format!(
"๐ SetDelegateCommission( coldkey:{:?}, hotkey:{:?}, commission:{:?} )",
coldkey, hotkey, commission
));
Ok(Response::default()
.add_attribute("action", "set_delegate_commission")
.add_attribute("hotkey", hotkey)
.add_attribute("commission", format!("{}", commission)))
}
pub fn hotkey_is_delegate(store: &dyn Storage, hotkey: &Addr) -> bool {
DELEGATES.has(store, hotkey)
}
pub fn delegate_hotkey(store: &mut dyn Storage, hotkey: &Addr, take: u16) {
DELEGATES
.save(store, hotkey, &take)
.expect("failed to save delegate");
}
#[cfg(test)]
pub fn get_total_stake(store: &dyn Storage) -> u64 {
return TOTAL_STAKE.load(store).unwrap();
}
#[cfg(test)]
pub fn increase_total_stake(store: &mut dyn Storage, increment: u64) {
TOTAL_STAKE
.update(store, |s| -> StdResult<_> {
Ok(s.saturating_add(increment))
})
.unwrap();
}
#[cfg(test)]
pub fn decrease_total_stake(store: &mut dyn Storage, decrement: u64) {
TOTAL_STAKE
.update(store, |s| -> StdResult<_> {
Ok(s.saturating_sub(decrement))
})
.unwrap();
}
pub fn get_total_stake_for_hotkey(store: &dyn Storage, hotkey: &Addr) -> u64 {
TOTAL_HOTKEY_STAKE.load(store, hotkey).unwrap_or_default()
}
#[cfg(test)]
pub fn get_total_stake_for_coldkey(store: &dyn Storage, coldkey: &Addr) -> u64 {
return TOTAL_COLDKEY_STAKE.load(store, coldkey).unwrap();
}
pub fn get_stake_for_coldkey_and_hotkey(store: &dyn Storage, coldkey: &Addr, hotkey: &Addr) -> u64 {
STAKE.load(store, (hotkey, coldkey)).unwrap_or_default()
}
pub fn create_account_if_non_existent(store: &mut dyn Storage, coldkey: &Addr, hotkey: &Addr) {
if !hotkey_account_exists(store, hotkey) {
STAKE
.save(store, (hotkey, coldkey), &0)
.expect("failed to save stake");
OWNER
.save(store, hotkey, coldkey)
.expect("failed to save owner");
TOTAL_HOTKEY_STAKE
.save(store, hotkey, &0u64)
.expect("failed to save hotkey stake");
TOTAL_COLDKEY_STAKE
.save(store, coldkey, &0u64)
.expect("failed to save coldkey stake");
}
}
pub fn get_owning_coldkey_for_hotkey(store: &dyn Storage, hotkey: &Addr) -> Addr {
OWNER.load(store, hotkey).expect("hotkey has no owner")
}
pub fn hotkey_account_exists(store: &dyn Storage, hotkey: &Addr) -> bool {
return OWNER.has(store, hotkey);
}
pub fn coldkey_owns_hotkey(store: &dyn Storage, coldkey: &Addr, hotkey: &Addr) -> bool {
OWNER
.may_load(store, hotkey)
.ok()
.flatten()
.map_or(false, |owner| owner == *coldkey)
}
pub fn has_enough_stake(
store: &dyn Storage,
coldkey: &Addr,
hotkey: &Addr,
decrement: u64,
) -> bool {
return get_stake_for_coldkey_and_hotkey(store, coldkey, hotkey) >= decrement;
}
pub fn increase_stake_on_hotkey_account(store: &mut dyn Storage, hotkey: &Addr, increment: u64) {
let coldkey = get_owning_coldkey_for_hotkey(store, hotkey);
increase_stake_on_coldkey_hotkey_account(store, &coldkey, hotkey, increment);
}
#[cfg(test)]
pub fn decrease_stake_on_hotkey_account(
store: &mut dyn Storage,
hotkey: &Addr,
decrement: u64,
) -> Result<(), ContractError> {
let coldkey = get_owning_coldkey_for_hotkey(store, hotkey);
decrease_stake_on_coldkey_hotkey_account(store, &coldkey, &hotkey, decrement)?;
Ok(())
}
pub fn increase_stake_on_coldkey_hotkey_account(
store: &mut dyn Storage,
coldkey: &Addr,
hotkey: &Addr,
increment: u64,
) {
TOTAL_COLDKEY_STAKE
.update(store, coldkey, |s| -> StdResult<_> {
Ok(s.unwrap_or_default().saturating_add(increment))
})
.expect("failed to update coldkey stake");
TOTAL_HOTKEY_STAKE
.update(store, hotkey, |s| -> StdResult<_> {
Ok(s.unwrap_or_default().saturating_add(increment))
})
.expect("failed to update hotkey stake");
STAKE
.update(store, (hotkey, coldkey), |s| -> StdResult<_> {
Ok(s.unwrap_or_default().saturating_add(increment))
})
.expect("failed to update stake");
TOTAL_STAKE
.update(store, |s| -> StdResult<_> {
Ok(s.saturating_add(increment))
})
.expect("failed to update total stake");
TOTAL_ISSUANCE
.update(store, |s| -> StdResult<_> {
Ok(s.saturating_add(increment))
})
.expect("failed to update total issuance");
}
pub fn decrease_stake_on_coldkey_hotkey_account(
store: &mut dyn Storage,
coldkey: &Addr,
hotkey: &Addr,
decrement: u64,
) -> Result<(), ContractError> {
TOTAL_COLDKEY_STAKE.update(store, coldkey, |s| -> StdResult<_> {
Ok(s.unwrap_or_default().saturating_sub(decrement))
})?;
TOTAL_HOTKEY_STAKE.update(store, hotkey, |s| -> StdResult<_> {
Ok(s.unwrap_or_default().saturating_sub(decrement))
})?;
STAKE.update(store, (hotkey, coldkey), |s| -> StdResult<_> {
Ok(s.unwrap_or_default().saturating_sub(decrement))
})?;
TOTAL_STAKE.update(store, |s| -> StdResult<_> {
Ok(s.saturating_sub(decrement))
})?;
TOTAL_ISSUANCE.update(store, |s| -> StdResult<_> {
Ok(s.saturating_sub(decrement))
})?;
Ok(())
}
pub fn unstake_all_coldkeys_from_hotkey_account(
store: &mut dyn Storage,
hotkey: &Addr,
) -> Result<Vec<CosmosMsg>, ContractError> {
let mut msgs: Vec<CosmosMsg> = Vec::new();
let stakes: Vec<(Addr, u64)> = STAKE
.prefix(hotkey)
.range(store.deref(), None, None, Order::Ascending)
.collect::<StdResult<Vec<_>>>()?;
for (delegate_coldkey_i, stake_i) in stakes {
if stake_i == 0 {
continue;
} else {
decrease_stake_on_coldkey_hotkey_account(store, &delegate_coldkey_i, &hotkey, stake_i)?;
let denom = DENOM.load(store)?;
msgs.push(CosmosMsg::Bank(BankMsg::Send {
to_address: delegate_coldkey_i.to_string(),
amount: coins(Uint128::from(stake_i).u128(), denom),
}));
}
}
Ok(msgs)
}