[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:
Dev Kalra 2023-03-13 15:30:48 +05:30 committed by GitHub
parent 085f08055a
commit 8ff1ada6dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 174 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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