Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: late initialization of astroport oracle contract #NTRN-429 #25

Merged
merged 5 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 88 additions & 35 deletions contracts/astroport/oracle/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use astroport::oracle::{ExecuteMsg, InstantiateMsg, QueryMsg};
use astroport::pair::TWAP_PRECISION;
use astroport::querier::{query_pair_info, query_token_precision};
use cosmwasm_std::{
entry_point, to_binary, Binary, Decimal256, Deps, DepsMut, Env, MessageInfo, Response,
StdError, StdResult, Uint128, Uint256, Uint64,
entry_point, to_binary, Binary, Decimal256, Deps, DepsMut, Env, MessageInfo, Response, Uint128,
Uint256, Uint64,
};
use cw2::set_contract_version;
use std::ops::Div;
Expand All @@ -22,40 +22,23 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
env: Env,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
msg.asset_infos[0].check(deps.api)?;
msg.asset_infos[1].check(deps.api)?;

set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

let factory_contract = addr_validate_to_lower(deps.api, &msg.factory_contract)?;
let pair_info = query_pair_info(&deps.querier, &factory_contract, &msg.asset_infos)?;

let config = Config {
owner: info.sender,
factory: factory_contract,
asset_infos: msg.asset_infos,
pair: pair_info.clone(),
asset_infos: None,
pair: None,
period: msg.period,
manager: deps.api.addr_validate(&msg.manager)?,
};
CONFIG.save(deps.storage, &config)?;
let prices = query_cumulative_prices(deps.querier, pair_info.contract_addr)?;
let average_prices = prices
.cumulative_prices
.iter()
.cloned()
.map(|(from, to, _)| (from, to, Decimal256::zero()))
.collect();

let price = PriceCumulativeLast {
cumulative_prices: prices.cumulative_prices,
average_prices,
block_timestamp_last: env.block.time.seconds(),
};
PRICE_LAST.save(deps.storage, &price, env.block.height)?;
LAST_UPDATE_HEIGHT.save(deps.storage, &Uint64::zero())?;
Ok(Response::default())
}
Expand All @@ -74,6 +57,8 @@ pub fn execute(
match msg {
ExecuteMsg::Update {} => update(deps, env),
ExecuteMsg::UpdatePeriod { new_period } => update_period(deps, env, info, new_period),
ExecuteMsg::UpdateManager { new_manager } => update_manager(deps, env, info, new_manager),
ExecuteMsg::SetAssetInfos(asset_infos) => set_asset_infos(deps, env, info, asset_infos),
}
}

Expand All @@ -96,12 +81,78 @@ pub fn update_period(
.add_attribute("new_period", config.period.to_string()))
}

pub fn update_manager(
deps: DepsMut,
_env: Env,
info: MessageInfo,
new_manager: String,
) -> Result<Response, ContractError> {
let mut config = CONFIG.load(deps.storage)?;
if info.sender != config.owner {
return Err(ContractError::Unauthorized {});
}

config.manager = deps.api.addr_validate(&new_manager)?;
CONFIG.save(deps.storage, &config)?;

Ok(Response::new()
.add_attribute("action", "update_manager")
.add_attribute("new_manager", new_manager))
}

pub fn set_asset_infos(
deps: DepsMut,
env: Env,
info: MessageInfo,
asset_infos: Vec<AssetInfo>,
) -> Result<Response, ContractError> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.manager {
return Err(ContractError::Unauthorized {});
}
if config.asset_infos != None {
return Err(ContractError::AssetInfosAlreadySet {});
}

asset_infos[0].check(deps.api)?;
asset_infos[1].check(deps.api)?;

let pair_info = query_pair_info(&deps.querier, &config.factory, &asset_infos)?;

let prices = query_cumulative_prices(deps.querier, &pair_info.contract_addr)?;
let average_prices = prices
.cumulative_prices
.iter()
.cloned()
.map(|(from, to, _)| (from, to, Decimal256::zero()))
.collect();
let price = PriceCumulativeLast {
cumulative_prices: prices.cumulative_prices,
average_prices,
block_timestamp_last: env.block.time.seconds(),
};
PRICE_LAST.save(deps.storage, &price, env.block.height)?;

let config = Config {
owner: config.owner,
factory: config.factory,
asset_infos: Some(asset_infos),
pair: Some(pair_info),
period: config.period,
manager: config.manager,
};
CONFIG.save(deps.storage, &config)?;

Ok(Response::default())
}

/// Updates the local TWAP values for the tokens in the target Astroport pool.
pub fn update(deps: DepsMut, env: Env) -> Result<Response, ContractError> {
let config = CONFIG.load(deps.storage)?;
let pair = config.pair.ok_or(ContractError::AssetInfosNotSet {})?;
let price_last = PRICE_LAST.load(deps.storage)?;

let prices = query_cumulative_prices(deps.querier, config.pair.contract_addr)?;
let prices = query_cumulative_prices(deps.querier, pair.contract_addr)?;
let time_elapsed = env.block.time.seconds() - price_last.block_timestamp_last;

// Ensure that at least one full period has passed since the last update
Expand Down Expand Up @@ -141,11 +192,11 @@ pub fn update(deps: DepsMut, env: Env) -> Result<Response, ContractError> {
/// * **QueryMsg::Consult { token, amount }** Validates assets and calculates a new average
/// amount with updated precision
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<Binary, ContractError> {
match msg {
QueryMsg::Consult { token, amount } => to_binary(&consult(deps, token, amount)?),
QueryMsg::Consult { token, amount } => Ok(to_binary(&consult(deps, token, amount)?)?),
QueryMsg::TWAPAtHeight { token, height } => {
to_binary(&twap_at_height(deps, token, height)?)
Ok(to_binary(&twap_at_height(deps, token, height)?)?)
}
}
}
Expand All @@ -158,8 +209,9 @@ fn consult(
deps: Deps,
token: AssetInfo,
amount: Uint128,
) -> Result<Vec<(AssetInfo, Uint256)>, StdError> {
) -> Result<Vec<(AssetInfo, Uint256)>, ContractError> {
let config = CONFIG.load(deps.storage)?;
let pair = config.pair.ok_or(ContractError::AssetInfosNotSet {})?;
let price_last = PRICE_LAST.load(deps.storage)?;

let mut average_prices = vec![];
Expand All @@ -170,7 +222,7 @@ fn consult(
}

if average_prices.is_empty() {
return Err(StdError::generic_err("Invalid Token"));
return Err(ContractError::InvalidToken {});
}

// Get the token's precision
Expand All @@ -183,7 +235,7 @@ fn consult(
if price_average.is_zero() {
let price = query_prices(
deps.querier,
config.pair.contract_addr.clone(),
pair.contract_addr.clone(),
Asset {
info: token.clone(),
amount: one,
Expand All @@ -203,7 +255,7 @@ fn consult(
))
}
})
.collect::<Result<Vec<(AssetInfo, Uint256)>, StdError>>()
.collect::<Result<Vec<(AssetInfo, Uint256)>, ContractError>>()
}

/// Returns token TWAP value for given height.
Expand All @@ -214,8 +266,9 @@ fn twap_at_height(
deps: Deps,
token: AssetInfo,
height: Uint64,
) -> Result<Vec<(AssetInfo, Decimal256)>, StdError> {
) -> Result<Vec<(AssetInfo, Decimal256)>, ContractError> {
let config = CONFIG.load(deps.storage)?;
let pair = config.pair.ok_or(ContractError::AssetInfosNotSet {})?;
let last_height = LAST_UPDATE_HEIGHT.load(deps.storage)?;
let mut query_height = height;
// if requested height > last snapshoted time, SnapshotItem.may_load_at_height() will return primary (default) value
Expand All @@ -235,7 +288,7 @@ fn twap_at_height(
}

if average_prices.is_empty() {
return Err(StdError::generic_err("Invalid Token"));
return Err(ContractError::InvalidToken {});
}

// Get the token's precision
Expand All @@ -248,7 +301,7 @@ fn twap_at_height(
if price_average.is_zero() {
let price = query_prices(
deps.querier,
config.pair.contract_addr.clone(),
pair.contract_addr.clone(),
Asset {
info: token.clone(),
amount: one,
Expand All @@ -266,5 +319,5 @@ fn twap_at_height(
Ok((asset.clone(), *price_average / price_precision))
}
})
.collect::<Result<Vec<(AssetInfo, Decimal256)>, StdError>>()
.collect::<Result<Vec<(AssetInfo, Decimal256)>, ContractError>>()
}
11 changes: 10 additions & 1 deletion contracts/astroport/oracle/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use cosmwasm_std::StdError;
use thiserror::Error;

/// This enum describes oracle contract errors
#[derive(Error, Debug)]
#[derive(PartialEq, Error, Debug)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
Expand All @@ -15,4 +15,13 @@ pub enum ContractError {

#[error("Contract can't be migrated!")]
MigrationError {},

#[error("Asset infos are not set")]
AssetInfosNotSet {},

#[error("Asset infos are already set")]
AssetInfosAlreadySet {},

#[error("Invalid token")]
InvalidToken {},
}
6 changes: 4 additions & 2 deletions contracts/astroport/oracle/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ pub struct Config {
/// The factory contract address
pub factory: Addr,
/// The assets in the pool. Each asset is described using a [`AssetInfo`]
pub asset_infos: Vec<AssetInfo>,
pub asset_infos: Option<Vec<AssetInfo>>,
/// Information about the pair (LP token address, pair type etc)
pub pair: PairInfo,
pub pair: Option<PairInfo>,
/// Time between two consecutive TWAP updates.
pub period: u64,
/// Manager is the only one who can set pair info, if not set already
pub manager: Addr,
}
59 changes: 57 additions & 2 deletions contracts/astroport/oracle/src/testing.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::contract::{execute, instantiate};
use crate::error::ContractError;
use crate::mock_querier::mock_dependencies;
use astroport::asset::{Asset, AssetInfo};
use astroport::oracle::{ExecuteMsg, InstantiateMsg};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{Addr, Decimal256, Uint128, Uint256};
use cosmwasm_std::{Addr, Decimal256, DepsMut, Env, MessageInfo, Uint128, Uint256};
use std::ops::Mul;

#[test]
Expand Down Expand Up @@ -52,8 +53,8 @@ fn oracle_overflow() {

let instantiate_msg = InstantiateMsg {
factory_contract: factory.to_string(),
asset_infos: vec![astro_asset_info, usdc_asset_info],
period: 1,
manager: String::from("manager"),
};

// Set cumulative price to 192738282u128
Expand All @@ -76,6 +77,13 @@ fn oracle_overflow() {
);
let res = instantiate(deps.as_mut(), env.clone(), info.clone(), instantiate_msg).unwrap();
assert_eq!(0, res.messages.len());
execute(
deps.as_mut(),
env.clone(),
mock_info("manager", &[]),
ExecuteMsg::SetAssetInfos(vec![astro_asset_info, usdc_asset_info]),
)
.unwrap();
// Set cumulative price to 100 (overflow)
deps.querier.set_cumulative_price(
Addr::unchecked("pair"),
Expand All @@ -97,3 +105,50 @@ fn oracle_overflow() {
env.block.time = env.block.time.plus_seconds(86400);
execute(deps.as_mut(), env, info, ExecuteMsg::Update {}).unwrap();
}

fn setup(deps: DepsMut, env: Env, info: MessageInfo) {
instantiate(
deps,
env,
info,
InstantiateMsg {
factory_contract: String::from("factory"),
period: 0,
manager: String::from("manager"),
},
)
.unwrap();
}

#[test]
fn update_does_not_work_without_pair_info() {
let mut deps = mock_dependencies(&[]);
let env = mock_env();
setup(deps.as_mut(), env.clone(), mock_info("dao", &[]));

for caller in ["someone", "dao"] {
let res = execute(
deps.as_mut(),
env.clone(),
mock_info(caller, &[]),
ExecuteMsg::Update {},
)
.unwrap_err();
assert_eq!(res, ContractError::AssetInfosNotSet {});
}
}

#[test]
fn update_period_works_without_pair_info() {
let mut deps = mock_dependencies(&[]);
let env = mock_env();
setup(deps.as_mut(), env.clone(), mock_info("dao", &[]));

execute(
deps.as_mut(),
env,
mock_info("dao", &[]),
ExecuteMsg::UpdatePeriod { new_period: 0 },
)
.unwrap();
}
Loading