Explorar el Código

Pyth to Wormhole on Terra (#629)

* More checks in P2W on Terra

Change-Id: Icbe5d75504f947b741cee1c797740b71456964fe

* Auto-deploy P2W on Terra

Change-Id: I202536fd278aca938e3b8b3cb0a4ceeca314158f

* Don't do replay protection on price updates

We already use the sequence number for replay and rollback protection and can save storage this way

Change-Id: I9e655956aab1ed8dd86b9d821ece2f57900f6c78
Hendrik Hofstadt hace 3 años
padre
commit
a91fe7797d

+ 4 - 2
docs/devnet.md

@@ -17,10 +17,12 @@
 | Token Bridge       | SOL             | B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE                                                          |                                                                                                                                                                |
 | NFT Bridge         | SOL             | NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA                                                           |                                                                                                                                                                |
 | Migration Contract | SOL             | Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK                                                          |                                                                                                                                                                |
+| P2W Emitter        | SOL             | 8fuAZUxHecYLMC76ZNjYzwRybUiDv9LhkRQsAccEykLr                                                          |                                                                                                                                                                |
 | Test Wallet        | Terra           | terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v                                                          | Mnemonic: `notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius` |
 | Example Token      | Terra           | terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh                                                          | Tokens minted to Test Wallet                                                                                                                                   |
-| Bridge Core        | Terra           | terra18eezxhys9jwku67cm4w84xhnzt4xjj77w2qt62                                                          |                                                                                                                                                                |
-| Token Bridge       | Terra           | terra1hqrdl6wstt8qzshwc6mrumpjk9338k0l93hqyd                                                          |                                                                                                                                                                |
+| Bridge Core        | Terra           | terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5                                                          |                                                                                                                                                                |
+| Token Bridge       | Terra           | terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4                                                          |                                                                                                                                                                |
+| Pyth Bridge        | Terra           | terra1wgh6adn8geywx0v78zs9azrqtqdegufuegnwep                                                          |                                                                                                                                                                |
 | Governance Emitter | Universal       | 0x0000000000000000000000000000000000000000000000000000000000000004 / 11111111111111111111111111111115 | Emitter Chain: 0x01                                                                                                                                            |
 
 ### Terra

+ 22 - 6
terra/contracts/pyth-bridge/src/contract.rs

@@ -27,6 +27,8 @@ use crate::{
         config_read,
         price_info,
         price_info_read,
+        sequence,
+        sequence_read,
         ConfigInfo,
         UpgradeContract,
     },
@@ -65,8 +67,10 @@ pub fn instantiate(
         gov_address: msg.gov_address.as_slice().to_vec(),
         wormhole_contract: msg.wormhole_contract,
         pyth_emitter: msg.pyth_emitter.as_slice().to_vec(),
+        pyth_emitter_chain: msg.pyth_emitter_chain,
     };
     config(deps.storage).save(&state)?;
+    sequence(deps.storage).save(&0)?;
 
     Ok(Response::default())
 }
@@ -101,22 +105,34 @@ fn submit_vaa(
     let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?;
     let data = vaa.payload;
 
-    if vaa_archive_check(deps.storage, vaa.hash.as_slice()) {
-        return ContractError::VaaAlreadyExecuted.std_err();
-    }
-    vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
-
     // check if vaa is from governance
     if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
+        if vaa_archive_check(deps.storage, vaa.hash.as_slice()) {
+            return ContractError::VaaAlreadyExecuted.std_err();
+        }
+        vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
+        
         return handle_governance_payload(deps, env, &data);
     }
 
+    // IMPORTANT: VAA replay-protection is not implemented in this code-path
+    // Sequences are used to prevent replay or price rollbacks
+
     let message =
         PriceAttestation::deserialize(&data[..]).map_err(|_| ContractError::InvalidVAA.std())?;
-    if vaa.emitter_address != state.pyth_emitter {
+    if vaa.emitter_address != state.pyth_emitter || vaa.emitter_chain != state.pyth_emitter_chain {
         return ContractError::InvalidVAA.std_err();
     }
 
+    // Check sequence
+    let last_sequence = sequence_read(deps.storage).load()?;
+    if vaa.sequence <= last_sequence && last_sequence != 0 {
+        return Err(StdError::generic_err(
+            "price sequences need to be monotonically increasing",
+        ));
+    }
+    sequence(deps.storage).save(&vaa.sequence)?;
+
     // Update price
     price_info(deps.storage).save(&message.product_id.to_bytes()[..], &data)?;
 

+ 1 - 1
terra/contracts/pyth-bridge/src/lib.rs

@@ -4,4 +4,4 @@ extern crate lazy_static;
 pub mod contract;
 pub mod msg;
 pub mod state;
-pub mod types;
+pub mod types;

+ 3 - 6
terra/contracts/pyth-bridge/src/msg.rs

@@ -1,6 +1,4 @@
-use cosmwasm_std::{
-    Binary,
-};
+use cosmwasm_std::Binary;
 use schemars::JsonSchema;
 use serde::{
     Deserialize,
@@ -17,14 +15,13 @@ pub struct InstantiateMsg {
 
     pub wormhole_contract: HumanAddr,
     pub pyth_emitter: Binary,
+    pub pyth_emitter_chain: u16,
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum ExecuteMsg {
-    SubmitVaa {
-        data: Binary,
-    },
+    SubmitVaa { data: Binary },
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]

+ 10 - 1
terra/contracts/pyth-bridge/src/state.rs

@@ -25,6 +25,7 @@ type HumanAddr = String;
 
 pub static CONFIG_KEY: &[u8] = b"config";
 pub static PRICE_INFO_KEY: &[u8] = b"price_info";
+pub static SEQUENCE_KEY: &[u8] = b"sequence";
 
 // Guardian set information
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -35,6 +36,7 @@ pub struct ConfigInfo {
 
     pub wormhole_contract: HumanAddr,
     pub pyth_emitter: Vec<u8>,
+    pub pyth_emitter_chain: u16,
 }
 
 pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
@@ -45,6 +47,14 @@ pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
     singleton_read(storage, CONFIG_KEY)
 }
 
+pub fn sequence(storage: &mut dyn Storage) -> Singleton<u64> {
+    singleton(storage, SEQUENCE_KEY)
+}
+
+pub fn sequence_read(storage: &dyn Storage) -> ReadonlySingleton<u64> {
+    singleton_read(storage, SEQUENCE_KEY)
+}
+
 pub fn price_info(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
     bucket(storage, PRICE_INFO_KEY)
 }
@@ -53,7 +63,6 @@ pub fn price_info_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
     bucket_read(storage, PRICE_INFO_KEY)
 }
 
-
 pub struct UpgradeContract {
     pub new_contract: u64,
 }

+ 10 - 10
terra/contracts/pyth-bridge/src/types/mod.rs

@@ -1,9 +1,7 @@
 pub mod pyth_extensions;
 
 use std::{
-    convert::{
-        TryInto,
-    },
+    convert::TryInto,
     io::Read,
     mem,
 };
@@ -38,7 +36,9 @@ pub enum PayloadId {
 
 // On-chain data types
 
-#[derive(Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(
+    Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize,
+)]
 pub struct PriceAttestation {
     pub product_id: Pubkey,
     pub price_id: Pubkey,
@@ -58,7 +58,7 @@ impl PriceAttestation {
     pub fn serialize(&self) -> Vec<u8> {
         // A nifty trick to get us yelled at if we forget to serialize a field
         #[deny(warnings)]
-            let PriceAttestation {
+        let PriceAttestation {
             product_id,
             price_id,
             price_type,
@@ -131,7 +131,7 @@ impl PriceAttestation {
                 "Invalid magic {:02X?}, expected {:02X?}",
                 magic_vec, P2W_MAGIC,
             )
-                .into());
+            .into());
         }
 
         let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
@@ -143,7 +143,7 @@ impl PriceAttestation {
                 "Unsupported format version {}, expected {}",
                 version, P2W_FORMAT_VERSION
             )
-                .into());
+            .into());
         }
 
         let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
@@ -155,7 +155,7 @@ impl PriceAttestation {
                 payload_id_vec[0],
                 PayloadId::PriceAttestation as u8,
             )
-                .into());
+            .into());
         }
 
         let mut product_id_vec = vec![0u8; PUBKEY_LEN];
@@ -190,7 +190,8 @@ impl PriceAttestation {
         println!("twac OK");
         let mut confidence_interval_vec = vec![0u8; mem::size_of::<u64>()];
         bytes.read_exact(confidence_interval_vec.as_mut_slice())?;
-        let confidence_interval = u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
+        let confidence_interval =
+            u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
 
         let mut status_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
         bytes.read_exact(status_vec.as_mut_slice())?;
@@ -204,7 +205,6 @@ impl PriceAttestation {
             }
         };
 
-
         let mut corp_act_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
         bytes.read_exact(corp_act_vec.as_mut_slice())?;
         let corp_act = match corp_act_vec[0] {

+ 8 - 2
terra/contracts/pyth-bridge/src/types/pyth_extensions.rs

@@ -1,7 +1,11 @@
 //! This module contains 1:1 (or close) copies of selected Pyth types
 //! with quick and dirty enhancements.
 
-use std::{convert::TryInto, io::Read, mem};
+use std::{
+    convert::TryInto,
+    io::Read,
+    mem,
+};
 
 use pyth_client::{
     CorpAction,
@@ -80,7 +84,9 @@ impl From<&CorpAction> for P2WCorpAction {
 }
 
 /// 1:1 Copy of pyth_client::Ema with all-pub fields.
-#[derive(Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(
+    Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize,
+)]
 #[repr(C)]
 pub struct P2WEma {
     pub val: i64,

+ 35 - 0
terra/tools/deploy.js

@@ -54,6 +54,7 @@ async function main() {
     "cw20_wrapped.wasm": 4000000,
     "wormhole.wasm": 5000000,
     "token_bridge.wasm": 6000000,
+    "pyth_bridge.wasm": 5000000,
   };
 
   // Deploy all found WASM files and assign Code IDs.
@@ -194,6 +195,40 @@ async function main() {
       addresses["mock.wasm"] = address;
     });
 
+  const pythEmitterAddress =
+    "71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
+  const pythChain = 1;
+
+  // Instantiate Pyth over Wormhole
+  console.log("Instantiating Pyth over Wormhole");
+  await wallet
+    .createAndSignTx({
+      msgs: [
+        new MsgInstantiateContract(
+          wallet.key.accAddress,
+          wallet.key.accAddress,
+          codeIds["pyth_bridge.wasm"],
+          {
+            gov_chain: govChain,
+            gov_address: Buffer.from(govAddress, "hex").toString("base64"),
+            wormhole_contract: addresses["wormhole.wasm"],
+            pyth_emitter: Buffer.from(pythEmitterAddress, "hex").toString(
+              "base64"
+            ),
+            pyth_emitter_chain: pythChain,
+          }
+        ),
+      ],
+      memo: "",
+    })
+    .then((tx) => terra.tx.broadcast(tx))
+    .then((rs) => {
+      const address = /"contract_address","value":"([^"]+)/gm.exec(
+        rs.raw_log
+      )[1];
+      addresses["pyth_bridge.wasm"] = address;
+    });
+
   console.log(addresses);
 
   const registrations = [