use crate::error::ContractError;
use crate::msg::{ExecMsg, InstantiateMsg, QueryMsg};
use crate::msg::{PriceFeedReq, PriceFeedResponse, PriceFeedsResponse};
use crate::state::{Price, ADMIN, DENOM, PRICES, REQUEST_FEES};
use cosmwasm_std::{
coins, entry_point, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Reply,
ReplyOn, Response, StdError, StdResult, SubMsg, Uint128, WasmMsg,
};
use crate::error::ContractError::{
InsufficientFees, InvalidExecuteMsg, PriceDoesNotExist, PriceFeedExists, Unauthorized,
};
use cw2::set_contract_version;
use cw2::get_contract_version;
use crate::msg::MigrateMsg ;
use semver::Version;
const CONTRACT_NAME: &str = "crates.io:Yggdrasil-Data-Feeds";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
fn check_admin(deps: &DepsMut, info: MessageInfo) -> Result<bool, ContractError> {
let admin = ADMIN.load(deps.storage)?;
if info.sender != admin {
return Ok(false);
}
Ok(true)
}
fn get_request_fees(deps: &DepsMut) -> Result<Uint128, ContractError> {
let fees = REQUEST_FEES.load(deps.storage)?;
Ok(fees)
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
let admin = info.sender.to_string();
ADMIN.save(deps.storage, &info.sender)?;
DENOM.save(deps.storage, &msg.denom)?;
Ok(Response::new()
.add_attribute("action", "instantiate")
.add_attribute("admin", admin))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecMsg,
) -> Result<Response, ContractError> {
match msg {
ExecMsg::UpdatePrice { symbol, price } => try_update_price(deps, info, symbol, price),
ExecMsg::RequestPriceFeed { symbol } => try_request_price(deps, info, symbol),
ExecMsg::RequestPriceFeeds { request } => try_request_price_feeds(deps, info, request),
ExecMsg::ChangeAdmin { address } => try_change_admin(deps, info, address),
ExecMsg::SetCostPerRequest { cost_per_request } => {
execute_set_cost(deps, info, cost_per_request)
}
_ => Err(InvalidExecuteMsg {}),
}
}
fn execute_set_cost(
deps: DepsMut,
info: MessageInfo,
cost_per_request: Uint128,
) -> Result<Response, ContractError> {
if check_admin(&deps, info).unwrap() {
REQUEST_FEES.save(deps.storage, &cost_per_request)?;
Ok(Response::new().add_attribute("action", "set_cost_per_request"))
} else {
return Err(Unauthorized {
function: "set_cost_per_request".to_string(),
});
}
}
fn try_change_admin(
deps: DepsMut,
info: MessageInfo,
address: String,
) -> Result<Response, ContractError> {
if check_admin(&deps, info).unwrap() {
let admin = deps.api.addr_validate(&address)?;
ADMIN.save(deps.storage, &admin)?;
Ok(Response::new()
.add_attribute("action", "change_admin")
.add_attribute("admin", address))
} else {
return Err(Unauthorized {
function: "change_admin".to_string(),
});
}
}
fn try_update_price(
deps: DepsMut,
info: MessageInfo,
symbol: String,
price: Price,
) -> Result<Response, ContractError> {
if check_admin(&deps, info).unwrap() {
PRICES.update(
deps.storage,
&symbol,
|old| -> Result<Price, ContractError> {
match old {
Some(_) => Ok(price.clone()),
None => Ok(price.clone())
}
},
)?;
Ok(Response::new()
.add_attribute("action", "update_price")
.add_attribute("symbol", symbol))
} else {
return Err(Unauthorized {
function: "update_price".to_string(),
});
}
}
pub fn try_request_price(
deps: DepsMut,
info: MessageInfo,
symbol: String,
) -> Result<Response, ContractError> {
let price = PRICES.load(deps.storage, &symbol)?;
let denom = DENOM.load(deps.storage)?;
let cost = get_request_fees(&deps).unwrap();
let sent_funds = info.funds.iter().find(|c| c.denom == denom);
match sent_funds {
Some(coin) if coin.amount >= cost => {
let bank_msg = BankMsg::Send {
to_address: ADMIN.load(deps.storage)?.into_string(),
amount: coins(coin.amount.u128(), &coin.denom),
};
let price_response = PriceFeedResponse {
symbol,
price: price.price,
};
let callback_msg = SubMsg {
msg: WasmMsg::Execute {
contract_addr: info.sender.clone().to_string(),
msg: to_json_binary(&ExecMsg::ReceivePrice { price_response })?,
funds: vec![],
}
.into(),
gas_limit: None,
id: 1,
reply_on: ReplyOn::Success,
payload: Binary::default(),
};
Ok(Response::new()
.add_message(bank_msg)
.add_submessage(callback_msg))
}
_ => Err(InsufficientFees {
fees: info.funds[0].amount.u128(),
}),
}
}
pub fn try_request_price_feeds(
deps: DepsMut,
info: MessageInfo,
req: PriceFeedReq,
) -> Result<Response, ContractError> {
let pairs = req.pairs;
let denom = DENOM.load(deps.storage)?;
let cost = get_request_fees(&deps).unwrap() * Uint128::new(pairs.len() as u128);
let prices: StdResult<Vec<PriceFeedResponse>> = pairs
.iter()
.map(|pair| {
if !PRICES.has(deps.storage, &pair) {
Ok(PriceFeedResponse {
symbol: pair.clone(),
price: Default::default(), })
} else {
let price = PRICES.load(deps.storage, pair)?;
Ok(PriceFeedResponse {
symbol: pair.clone(),
price: price.price,
})
}
})
.collect();
let prices = prices?;
let sent_funds = info.funds.iter().find(|c| c.denom == denom);
match sent_funds {
Some(coin) if coin.amount >= cost => {
let bank_msg = BankMsg::Send {
to_address: ADMIN.load(deps.storage)?.into_string(),
amount: coins(coin.amount.u128(), &coin.denom),
};
let prices_response = PriceFeedsResponse {
price_feeds: prices,
};
let callback_msg = SubMsg {
msg: WasmMsg::Execute {
contract_addr: info.sender.clone().to_string(),
msg: to_json_binary(&ExecMsg::ReceivePrices { prices_response })?,
funds: vec![],
}
.into(),
gas_limit: None,
id: 2,
reply_on: ReplyOn::Success,
payload: Binary::default(),
};
Ok(Response::new()
.add_message(bank_msg)
.add_submessage(callback_msg))
}
_ => Err(InsufficientFees {
fees: info.funds[0].amount.u128(),
}),
}
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetAllSymbols {} => to_json_binary(&query_all_symbols(deps)?),
}
}
fn query_all_symbols(deps: Deps) -> StdResult<Vec<String>> {
PRICES
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect()
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> {
match msg.id {
1 => Ok(Response::new().add_attribute("action", "price_feed_reply")),
2 => Ok(Response::new().add_attribute("action", "price_feeds_reply")),
_ => Err(StdError::generic_err("Unknown reply ID")),
}
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
let version: Version = CONTRACT_VERSION.parse().unwrap();
let storage_version: Version = get_contract_version(deps.storage)?.version.parse().unwrap();
if storage_version < version {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
}
Ok(Response::default())
}