[cosmwasm] Injective cosmwasm (#680)
* update process_batch_attestation * add type to Response * add injective's support * add cfg feature * compilation feature flag * change price status to pyth status * update README * update injective's struct
This commit is contained in:
parent
085f08055a
commit
8ff1ada6dc
|
@ -1270,6 +1270,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"sha3",
|
||||
"terraswap",
|
||||
"thiserror",
|
||||
|
@ -1591,6 +1592,17 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395627de918015623b32e7669714206363a7fc00382bf477e72c1f7533e8eafc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.9"
|
||||
|
|
|
@ -18,6 +18,14 @@ This directory contains the code to perform all the steps. Read below for the de
|
|||
|
||||
First, build the contracts within [the current directory](./). You must have Docker installed.
|
||||
|
||||
NOTE: In order to build for Injective. We need to enable a feature in [Cargo.toml](./contracts/pyth/Cargo.toml) like this.
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default=["injective"]
|
||||
...
|
||||
```
|
||||
|
||||
```sh
|
||||
bash build.sh
|
||||
```
|
||||
|
|
|
@ -9,9 +9,12 @@ description = "Pyth price receiver"
|
|||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# IMPORTANT: if you want to build for injective, enable the default feature below
|
||||
# default=["injective"]
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
# use library feature to disable all init/handle/query exports
|
||||
library = []
|
||||
injective = ["dep:serde_repr"]
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
|
@ -19,6 +22,7 @@ cosmwasm-storage = { version = "1.0.0" }
|
|||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
serde_derive = { version = "1.0.103"}
|
||||
serde_repr = { version="0.1", optional = true}
|
||||
terraswap = "2.4.0"
|
||||
wormhole-bridge-terra-2 = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.14.8", features = ["library"] }
|
||||
thiserror = { version = "1.0.20" }
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
#[cfg(feature = "injective")]
|
||||
use crate::injective::{
|
||||
create_relay_pyth_prices_msg,
|
||||
InjectiveMsgWrapper as MsgWrapper,
|
||||
};
|
||||
#[cfg(not(feature = "injective"))]
|
||||
use cosmwasm_std::Empty as MsgWrapper;
|
||||
use {
|
||||
crate::{
|
||||
governance::{
|
||||
|
@ -128,7 +135,12 @@ pub fn parse_and_verify_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> St
|
|||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
|
||||
pub fn execute(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> StdResult<Response<MsgWrapper>> {
|
||||
match msg {
|
||||
ExecuteMsg::UpdatePriceFeeds { data } => update_price_feeds(deps, env, info, &data),
|
||||
ExecuteMsg::ExecuteGovernanceInstruction { data } => {
|
||||
|
@ -147,7 +159,7 @@ fn update_price_feeds(
|
|||
env: Env,
|
||||
info: MessageInfo,
|
||||
data: &[Binary],
|
||||
) -> StdResult<Response> {
|
||||
) -> StdResult<Response<MsgWrapper>> {
|
||||
let state = config_read(deps.storage).load()?;
|
||||
|
||||
// Check that a sufficient fee was sent with the message
|
||||
|
@ -157,8 +169,9 @@ fn update_price_feeds(
|
|||
return Err(PythContractError::InsufficientFee.into());
|
||||
}
|
||||
|
||||
let mut total_attestations: usize = 0;
|
||||
let mut new_attestations: usize = 0;
|
||||
let mut num_total_attestations: usize = 0;
|
||||
let mut total_new_attestations: Vec<PriceAttestation> = vec![];
|
||||
|
||||
for datum in data {
|
||||
let vaa = parse_and_verify_vaa(deps.branch(), env.block.time.seconds(), datum)?;
|
||||
verify_vaa_from_data_source(&state, &vaa)?;
|
||||
|
@ -167,16 +180,36 @@ fn update_price_feeds(
|
|||
let batch_attestation = BatchPriceAttestation::deserialize(&data[..])
|
||||
.map_err(|_| PythContractError::InvalidUpdatePayload)?;
|
||||
|
||||
let (num_updates, num_new) =
|
||||
let (num_attestations, new_attestations) =
|
||||
process_batch_attestation(&mut deps, &env, &batch_attestation)?;
|
||||
total_attestations += num_updates;
|
||||
new_attestations += num_new;
|
||||
num_total_attestations += num_attestations;
|
||||
for new_attestation in new_attestations {
|
||||
total_new_attestations.push(new_attestation.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "update_price_feeds")
|
||||
.add_attribute("num_attestations", format!("{total_attestations}"))
|
||||
.add_attribute("num_updated", format!("{new_attestations}")))
|
||||
let num_total_new_attestations = total_new_attestations.len();
|
||||
|
||||
let response = Response::new();
|
||||
|
||||
#[cfg(feature = "injective")]
|
||||
{
|
||||
let inj_message =
|
||||
create_relay_pyth_prices_msg(env.contract.address, total_new_attestations);
|
||||
Ok(response
|
||||
.add_message(inj_message)
|
||||
.add_attribute("action", "update_price_feeds")
|
||||
.add_attribute("num_attestations", format!("{num_total_attestations}"))
|
||||
.add_attribute("num_updated", format!("{num_total_new_attestations}")))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "injective"))]
|
||||
{
|
||||
Ok(response
|
||||
.add_attribute("action", "update_price_feeds")
|
||||
.add_attribute("num_attestations", format!("{num_total_attestations}"))
|
||||
.add_attribute("num_updated", format!("{num_total_new_attestations}")))
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a governance instruction provided as the VAA `data`.
|
||||
|
@ -187,7 +220,7 @@ fn execute_governance_instruction(
|
|||
env: Env,
|
||||
_info: MessageInfo,
|
||||
data: &Binary,
|
||||
) -> StdResult<Response> {
|
||||
) -> StdResult<Response<MsgWrapper>> {
|
||||
let vaa = parse_and_verify_vaa(deps.branch(), env.block.time.seconds(), data)?;
|
||||
let state = config_read(deps.storage).load()?;
|
||||
verify_vaa_from_governance_source(&state, &vaa)?;
|
||||
|
@ -282,7 +315,7 @@ fn transfer_governance(
|
|||
next_config: &mut ConfigInfo,
|
||||
current_config: &ConfigInfo,
|
||||
parsed_claim_vaa: &ParsedVAA,
|
||||
) -> StdResult<Response> {
|
||||
) -> StdResult<Response<MsgWrapper>> {
|
||||
let claim_vaa_instruction =
|
||||
GovernanceInstruction::deserialize(parsed_claim_vaa.payload.as_slice())
|
||||
.map_err(|_| PythContractError::InvalidGovernancePayload)?;
|
||||
|
@ -335,7 +368,7 @@ fn transfer_governance(
|
|||
/// Upgrades the contract at `address` to `new_code_id` (by sending a `Migrate` message). The
|
||||
/// migration will fail unless this contract is the admin of the contract being upgraded.
|
||||
/// (Typically, `address` is this contract's address, and the contract is its own admin.)
|
||||
fn upgrade_contract(address: &Addr, new_code_id: u64) -> StdResult<Response> {
|
||||
fn upgrade_contract(address: &Addr, new_code_id: u64) -> StdResult<Response<MsgWrapper>> {
|
||||
Ok(Response::new()
|
||||
.add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
|
||||
contract_addr: address.to_string(),
|
||||
|
@ -371,26 +404,23 @@ fn verify_vaa_from_governance_source(state: &ConfigInfo, vaa: &ParsedVAA) -> Std
|
|||
}
|
||||
|
||||
/// Update the on-chain storage for any new price updates provided in `batch_attestation`.
|
||||
fn process_batch_attestation(
|
||||
fn process_batch_attestation<'a>(
|
||||
deps: &mut DepsMut,
|
||||
env: &Env,
|
||||
batch_attestation: &BatchPriceAttestation,
|
||||
) -> StdResult<(usize, usize)> {
|
||||
let mut new_attestations_cnt: usize = 0;
|
||||
batch_attestation: &'a BatchPriceAttestation,
|
||||
) -> StdResult<(usize, Vec<&'a PriceAttestation>)> {
|
||||
let mut new_attestations = vec![];
|
||||
|
||||
// Update prices
|
||||
for price_attestation in batch_attestation.price_attestations.iter() {
|
||||
let price_feed = create_price_feed_from_price_attestation(price_attestation);
|
||||
|
||||
if update_price_feed_if_new(deps, env, price_feed)? {
|
||||
new_attestations_cnt += 1;
|
||||
new_attestations.push(price_attestation);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
batch_attestation.price_attestations.len(),
|
||||
new_attestations_cnt,
|
||||
))
|
||||
Ok((batch_attestation.price_attestations.len(), new_attestations))
|
||||
}
|
||||
|
||||
fn create_price_feed_from_price_attestation(price_attestation: &PriceAttestation) -> PriceFeed {
|
||||
|
@ -691,7 +721,7 @@ mod test {
|
|||
emitter_address: &[u8],
|
||||
emitter_chain: u16,
|
||||
funds: &[Coin],
|
||||
) -> StdResult<Response> {
|
||||
) -> StdResult<Response<MsgWrapper>> {
|
||||
let (mut deps, env) = setup_test();
|
||||
config(&mut deps.storage).save(config_info).unwrap();
|
||||
|
||||
|
@ -706,11 +736,11 @@ mod test {
|
|||
let attestations = BatchPriceAttestation {
|
||||
price_attestations: vec![],
|
||||
};
|
||||
let (total_attestations, new_attestations) =
|
||||
let (num_attestations, new_attestations) =
|
||||
process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap();
|
||||
|
||||
assert_eq!(total_attestations, 0);
|
||||
assert_eq!(new_attestations, 0);
|
||||
assert_eq!(num_attestations, 0);
|
||||
assert_eq!(new_attestations.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -824,7 +854,7 @@ mod test {
|
|||
let attestations = BatchPriceAttestation {
|
||||
price_attestations: vec![price_attestation],
|
||||
};
|
||||
let (total_attestations, new_attestations) =
|
||||
let (num_attestations, new_attestations) =
|
||||
process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap();
|
||||
|
||||
|
||||
|
@ -834,8 +864,8 @@ mod test {
|
|||
let price = stored_price_feed.get_price_unchecked();
|
||||
let ema_price = stored_price_feed.get_ema_price_unchecked();
|
||||
|
||||
assert_eq!(total_attestations, 1);
|
||||
assert_eq!(new_attestations, 1);
|
||||
assert_eq!(num_attestations, 1);
|
||||
assert_eq!(new_attestations.len(), 1);
|
||||
|
||||
// for price
|
||||
assert_eq!(price.price, 99);
|
||||
|
@ -876,7 +906,7 @@ mod test {
|
|||
let attestations = BatchPriceAttestation {
|
||||
price_attestations: vec![price_attestation],
|
||||
};
|
||||
let (total_attestations, new_attestations) =
|
||||
let (num_attestations, new_attestations) =
|
||||
process_batch_attestation(&mut deps.as_mut(), &env, &attestations).unwrap();
|
||||
|
||||
|
||||
|
@ -886,8 +916,8 @@ mod test {
|
|||
let price = stored_price_feed.get_price_unchecked();
|
||||
let ema_price = stored_price_feed.get_ema_price_unchecked();
|
||||
|
||||
assert_eq!(total_attestations, 1);
|
||||
assert_eq!(new_attestations, 1);
|
||||
assert_eq!(num_attestations, 1);
|
||||
assert_eq!(new_attestations.len(), 1);
|
||||
|
||||
// for price
|
||||
assert_eq!(price.price, 100);
|
||||
|
@ -1147,7 +1177,7 @@ mod test {
|
|||
fn apply_governance_vaa(
|
||||
initial_config: &ConfigInfo,
|
||||
vaa: &ParsedVAA,
|
||||
) -> StdResult<(Response, ConfigInfo)> {
|
||||
) -> StdResult<(Response<MsgWrapper>, ConfigInfo)> {
|
||||
let (mut deps, env) = setup_test();
|
||||
config(&mut deps.storage).save(initial_config).unwrap();
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
use {
|
||||
cosmwasm_std::{
|
||||
Addr,
|
||||
CosmosMsg,
|
||||
CustomMsg,
|
||||
},
|
||||
pyth_wormhole_attester_sdk::PriceAttestation,
|
||||
schemars::JsonSchema,
|
||||
serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct InjectivePriceAttestation {
|
||||
pub price_id: String,
|
||||
pub price: i64,
|
||||
pub conf: u64,
|
||||
pub expo: i32,
|
||||
pub ema_price: i64,
|
||||
pub ema_conf: u64,
|
||||
pub ema_expo: i32,
|
||||
pub publish_time: i64,
|
||||
}
|
||||
|
||||
impl From<&PriceAttestation> for InjectivePriceAttestation {
|
||||
fn from(pa: &PriceAttestation) -> Self {
|
||||
InjectivePriceAttestation {
|
||||
price_id: pa.price_id.to_hex(),
|
||||
price: pa.price,
|
||||
conf: pa.conf,
|
||||
expo: pa.expo,
|
||||
ema_price: pa.ema_price,
|
||||
ema_conf: pa.ema_conf,
|
||||
ema_expo: pa.expo,
|
||||
publish_time: pa.publish_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum InjectiveMsg {
|
||||
RelayPythPrices {
|
||||
sender: Addr,
|
||||
price_attestations: Vec<InjectivePriceAttestation>,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct InjectiveMsgWrapper {
|
||||
pub route: String,
|
||||
pub msg_data: InjectiveMsg,
|
||||
}
|
||||
|
||||
pub fn create_relay_pyth_prices_msg(
|
||||
sender: Addr,
|
||||
price_attestations: Vec<PriceAttestation>,
|
||||
) -> CosmosMsg<InjectiveMsgWrapper> {
|
||||
InjectiveMsgWrapper {
|
||||
route: "oracle".to_string(),
|
||||
msg_data: InjectiveMsg::RelayPythPrices {
|
||||
sender,
|
||||
price_attestations: price_attestations
|
||||
.iter()
|
||||
.map(InjectivePriceAttestation::from)
|
||||
.collect(),
|
||||
},
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
impl From<InjectiveMsgWrapper> for CosmosMsg<InjectiveMsgWrapper> {
|
||||
fn from(s: InjectiveMsgWrapper) -> CosmosMsg<InjectiveMsgWrapper> {
|
||||
CosmosMsg::Custom(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomMsg for InjectiveMsgWrapper {
|
||||
}
|
|
@ -5,3 +5,6 @@ pub mod contract;
|
|||
pub mod governance;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
|
||||
#[cfg(feature = "injective")]
|
||||
mod injective;
|
||||
|
|
Loading…
Reference in New Issue