Bladeren bron

[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
Dev Kalra 2 jaren geleden
bovenliggende
commit
8ff1ada6dc

+ 12 - 0
target_chains/cosmwasm/Cargo.lock

@@ -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"

+ 8 - 0
target_chains/cosmwasm/README.md

@@ -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
 ```

+ 4 - 0
target_chains/cosmwasm/contracts/pyth/Cargo.toml

@@ -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" }

+ 64 - 34
target_chains/cosmwasm/contracts/pyth/src/contract.rs

@@ -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();
 

+ 83 - 0
target_chains/cosmwasm/contracts/pyth/src/injective.rs

@@ -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 {
+}

+ 3 - 0
target_chains/cosmwasm/contracts/pyth/src/lib.rs

@@ -5,3 +5,6 @@ pub mod contract;
 pub mod governance;
 pub mod msg;
 pub mod state;
+
+#[cfg(feature = "injective")]
+mod injective;