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
This commit is contained in:
parent
195a61714e
commit
a91fe7797d
|
@ -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
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
|
|
|
@ -4,4 +4,4 @@ extern crate lazy_static;
|
|||
pub mod contract;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
pub mod types;
|
||||
pub mod types;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = [
|
||||
|
|
Loading…
Reference in New Issue