pyth-crosschain/target_chains/near/receiver/tests/workspaces.rs

1207 lines
37 KiB
Rust

use {
byteorder::BigEndian,
near_sdk::json_types::U128,
pyth::{
governance::{
GovernanceAction,
GovernanceInstruction,
GovernanceModule,
},
state::{
Chain,
Price,
PriceIdentifier,
Source,
},
},
pyth_wormhole_attester_sdk::{
BatchPriceAttestation,
Identifier,
PriceAttestation,
PriceStatus,
},
pythnet_sdk::{
accumulators::{
merkle::MerkleTree,
Accumulator,
},
hashers::keccak256_160::Keccak160,
messages::{
Message,
PriceFeedMessage,
},
wire::{
to_vec,
v1::{
AccumulatorUpdateData,
MerklePriceUpdate,
Proof,
WormholeMerkleRoot,
WormholeMessage,
WormholePayload,
},
PrefixedVec,
},
},
serde_json::json,
std::io::{
Cursor,
Write,
},
wormhole_sdk::Chain as WormholeChain,
};
async fn initialize_chain() -> (
workspaces::Worker<workspaces::network::Sandbox>,
workspaces::Contract,
workspaces::Contract,
) {
let worker = workspaces::sandbox().await.expect("Workspaces Failed");
// Deploy Pyth
let contract = worker
.dev_deploy(&std::fs::read("pyth.wasm").expect("Failed to find pyth.wasm"))
.await
.expect("Failed to deploy pyth.wasm");
// Deploy Wormhole Stub, this is a dummy contract that always verifies VAA's correctly so we
// can test the ext_wormhole API.
let wormhole = worker
.dev_deploy(
&std::fs::read("wormhole_stub.wasm").expect("Failed to find wormhole_stub.wasm"),
)
.await
.expect("Failed to deploy wormhole_stub.wasm");
// Initialize Wormhole.
let _ = wormhole
.call("new")
.args_json(&json!({}))
.gas(300_000_000_000_000)
.transact_async()
.await
.expect("Failed to initialize Wormhole")
.await
.unwrap();
// Initialize Pyth, one time operation that sets the Wormhole contract address.
let codehash = [0u8; 32];
let _ = contract
.call("new")
.args_json(&json!({
"wormhole": wormhole.id(),
"codehash": codehash,
"initial_source": Source::default(),
"gov_source": Source::default(),
"update_fee": U128::from(1u128),
"stale_threshold": 32,
}))
.gas(300_000_000_000_000)
.transact_async()
.await
.expect("Failed to initialize Pyth")
.await
.unwrap();
(worker, contract, wormhole)
}
#[tokio::test]
async fn test_set_sources() {
let (_, contract, _) = initialize_chain().await;
// Submit a new Source to the contract, this will trigger a cross-contract call to wormhole
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
sequence: 1,
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Any),
module: GovernanceModule::Target,
action: GovernanceAction::SetDataSources {
data_sources: vec![
Source::default(),
Source {
emitter: [1; 32],
chain: Chain::from(WormholeChain::Solana),
},
],
},
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// There should now be a two sources in the contract state.
assert_eq!(
serde_json::from_slice::<Vec<Source>>(&contract.view("get_sources").await.unwrap().result)
.unwrap(),
&[
Source::default(),
Source {
emitter: [1; 32],
chain: Chain::from(WormholeChain::Solana),
},
]
);
}
#[tokio::test]
async fn test_set_governance_source() {
let (_, contract, _) = initialize_chain().await;
// Submit a new Source to the contract, this will trigger a cross-contract call to wormhole
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 2,
..Default::default()
};
let vaa = {
let request_vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Solana,
emitter_address: wormhole_sdk::Address([1; 32]),
payload: (),
sequence: 1,
..Default::default()
};
// Data Source Upgrades are submitted with an embedded VAA, generate that one here first
// before we embed it.
let request_vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &request_vaa).expect("Failed to serialize VAA");
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::RequestGovernanceDataSourceTransfer {
governance_data_source_index: 1,
},
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
cur.into_inner()
};
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::AuthorizeGovernanceDataSourceTransfer {
claim_vaa: request_vaa,
},
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact()
.await
.expect("Failed to submit VAA")
.unwrap()
.failures()
.is_empty());
// An action from the new source should now be accepted.
let vaa = wormhole_sdk::Vaa {
sequence: 3, // NOTE: Incremented Governance Sequence
emitter_chain: wormhole_sdk::Chain::Solana,
emitter_address: wormhole_sdk::Address([1; 32]),
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetDataSources {
data_sources: vec![
Source::default(),
Source {
emitter: [2; 32],
chain: Chain::from(WormholeChain::Solana),
},
],
},
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// But not from the old source.
let vaa = wormhole_sdk::Vaa {
sequence: 4, // NOTE: Incremented Governance Sequence
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetDataSources {
data_sources: vec![
Source::default(),
Source {
emitter: [2; 32],
chain: Chain::from(WormholeChain::Solana),
},
],
},
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.outcome()
.is_success());
}
#[tokio::test]
async fn test_stale_threshold() {
let (_, contract, _) = initialize_chain().await;
// Submit a Price Attestation to the contract.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 1,
..Default::default()
};
// Get current UNIX timestamp and subtract a minute from it to place the price attestation in
// the past. This should be accepted but untrusted.
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Failed to get UNIX timestamp")
.as_secs()
- 60;
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&BatchPriceAttestation {
price_attestations: vec![PriceAttestation {
product_id: Identifier::default(),
price_id: Identifier::default(),
price: 100,
conf: 1,
expo: 8,
ema_price: 100,
ema_conf: 1,
status: PriceStatus::Trading,
num_publishers: 8,
max_num_publishers: 8,
attestation_time: now.try_into().unwrap(),
publish_time: now.try_into().unwrap(),
prev_publish_time: now.try_into().unwrap(),
prev_price: 100,
prev_conf: 1,
last_attested_publish_time: now.try_into().unwrap(),
}],
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
let update_fee = serde_json::from_slice::<U128>(
&contract
.view("get_update_fee_estimate")
.args_json(&json!({
"data": vaa,
}))
.await
.unwrap()
.result,
)
.unwrap();
// Submit price. As there are no prices this should succeed despite being old.
assert!(contract
.call("update_price_feeds")
.gas(300_000_000_000_000)
.deposit(update_fee.into())
.args_json(&json!({
"data": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Despite succeeding, assert Price cannot be requested, 60 seconds in the past should be
// considered stale. [tag:failed_price_check]
assert_eq!(
None,
serde_json::from_slice::<Option<Price>>(
&contract
.view("get_price")
.args_json(&json!({ "price_identifier": PriceIdentifier([0; 32]) }))
.await
.unwrap()
.result
)
.unwrap(),
);
// Submit another Price Attestation to the contract with an even older timestamp. Which
// should now fail due to the existing newer price.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
sequence: 2,
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&BatchPriceAttestation {
price_attestations: vec![PriceAttestation {
product_id: Identifier::default(),
price_id: Identifier::default(),
price: 1000,
conf: 1,
expo: 8,
ema_price: 1000,
ema_conf: 1,
status: PriceStatus::Trading,
num_publishers: 8,
max_num_publishers: 8,
attestation_time: (now - 1024).try_into().unwrap(),
publish_time: (now - 1024).try_into().unwrap(),
prev_publish_time: (now - 1024).try_into().unwrap(),
prev_price: 90,
prev_conf: 1,
last_attested_publish_time: (now - 1024).try_into().unwrap(),
}],
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
// The update handler should now succeed even if price is old, but simply not update the price.
assert!(contract
.call("update_price_feeds")
.gas(300_000_000_000_000)
.deposit(update_fee.into())
.args_json(&json!({
"data": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// The price however should _not_ have updated and if we check the unsafe stored price the
// timestamp and price should be unchanged.
assert_eq!(
Price {
price: 100.into(),
conf: 1.into(),
expo: 8,
publish_time: now as i64,
},
serde_json::from_slice::<Price>(
&contract
.view("get_price_unsafe")
.args_json(&json!({ "price_identifier": PriceIdentifier([0; 32]) }))
.await
.unwrap()
.result
)
.unwrap(),
);
// Now we extend the staleness threshold with a Governance VAA.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
sequence: 3,
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).unwrap();
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetValidPeriod { valid_seconds: 256 },
}
.serialize()
.unwrap(),
)
.unwrap();
hex::encode(cur.into_inner())
};
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// It should now be possible to request the price that previously returned None.
// [ref:failed_price_check]
assert_eq!(
Some(Price {
price: 100.into(),
conf: 1.into(),
expo: 8,
publish_time: now as i64,
}),
serde_json::from_slice::<Option<Price>>(
&contract
.view("get_price")
.args_json(&json!({ "price_identifier": PriceIdentifier([0; 32]) }))
.await
.unwrap()
.result
)
.unwrap(),
);
}
#[tokio::test]
async fn test_contract_fees() {
let (_, contract, _) = initialize_chain().await;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Failed to get UNIX timestamp")
.as_secs();
// Set a high fee for the contract needed to submit a price.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 1,
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).unwrap();
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetFee { base: 128, expo: 8 },
}
.serialize()
.unwrap(),
)
.unwrap();
hex::encode(cur.into_inner())
};
// Fetch Update fee before changing it.
let update_fee = serde_json::from_slice::<U128>(
&contract
.view("get_update_fee_estimate")
.args_json(&json!({
"data": vaa,
}))
.await
.unwrap()
.result,
)
.unwrap();
// Now set the update_fee so that it is too high for the deposit to cover.
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Check the state has actually changed before we try and execute another VAA.
assert_ne!(
u128::from(update_fee),
u128::from(
serde_json::from_slice::<U128>(
&contract
.view("get_update_fee_estimate")
.args_json(&json!({
"data": vaa,
}))
.await
.unwrap()
.result,
)
.unwrap()
)
);
// Attempt to update the price feed with a now too low deposit.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
sequence: 2,
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&BatchPriceAttestation {
price_attestations: vec![PriceAttestation {
product_id: Identifier::default(),
price_id: Identifier::default(),
price: 1000,
conf: 1,
expo: 8,
ema_price: 1000,
ema_conf: 1,
status: PriceStatus::Trading,
num_publishers: 8,
max_num_publishers: 8,
attestation_time: (now - 1024).try_into().unwrap(),
publish_time: (now - 1024).try_into().unwrap(),
prev_publish_time: (now - 1024).try_into().unwrap(),
prev_price: 90,
prev_conf: 1,
last_attested_publish_time: (now - 1024).try_into().unwrap(),
}],
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
assert!(contract
.call("update_price_feeds")
.gas(300_000_000_000_000)
.deposit(update_fee.into())
.args_json(&json!({
"data": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Submitting a Price should have failed because the fee was not enough.
assert_eq!(
None,
serde_json::from_slice::<Option<Price>>(
&contract
.view("get_price")
.args_json(&json!({ "price_identifier": PriceIdentifier([0; 32]) }))
.await
.unwrap()
.result
)
.unwrap(),
);
}
// A test that attempts to SetFee twice with the same governance action, the first should succeed,
// the second should fail.
#[tokio::test]
async fn test_same_governance_sequence_fails() {
let (_, contract, _) = initialize_chain().await;
// Set a high fee for the contract needed to submit a price.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 1,
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).unwrap();
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetFee { base: 128, expo: 8 },
}
.serialize()
.unwrap(),
)
.unwrap();
hex::encode(cur.into_inner())
};
// Attempt our first SetFee.
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Attempt to run the same VAA again.
assert!(!contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
}
// A test that attempts to SetFee twice with the same governance action, the first should succeed,
// the second should fail.
#[tokio::test]
async fn test_out_of_order_sequences_fail() {
let (_, contract, _) = initialize_chain().await;
// Set a high fee for the contract needed to submit a price.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 1,
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).unwrap();
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetFee { base: 128, expo: 8 },
}
.serialize()
.unwrap(),
)
.unwrap();
hex::encode(cur.into_inner())
};
// Attempt our first SetFee.
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Generate another VAA with sequence 3.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 3,
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).unwrap();
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetFee { base: 128, expo: 8 },
}
.serialize()
.unwrap(),
)
.unwrap();
hex::encode(cur.into_inner())
};
// This should succeed.
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Generate another VAA with sequence 2.
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 2,
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).unwrap();
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Near),
module: GovernanceModule::Target,
action: GovernanceAction::SetFee { base: 128, expo: 8 },
}
.serialize()
.unwrap(),
)
.unwrap();
hex::encode(cur.into_inner())
};
// This should fail due to being out of order.
assert!(!contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
}
// A test that fails if the governance action payload target is not NEAR.
#[tokio::test]
async fn test_governance_target_fails_if_not_near() {
let (_, contract, _) = initialize_chain().await;
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
payload: (),
sequence: 1,
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).unwrap();
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Solana),
module: GovernanceModule::Target,
action: GovernanceAction::SetFee { base: 128, expo: 8 },
}
.serialize()
.unwrap(),
)
.unwrap();
hex::encode(cur.into_inner())
};
// This should fail as the target is Solana, when Near is expected.
assert!(!contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
}
// A test to check accumulator style updates work as intended.
#[tokio::test]
async fn test_accumulator_updates() {
fn create_dummy_price_feed_message(value: i64) -> Message {
let mut dummy_id = [0; 32];
dummy_id[0] = value as u8;
let msg = PriceFeedMessage {
feed_id: dummy_id,
price: value,
conf: value as u64,
exponent: value as i32,
publish_time: value,
prev_publish_time: value,
ema_price: value,
ema_conf: value as u64,
};
Message::PriceFeedMessage(msg)
}
fn create_accumulator_message_from_updates(
price_updates: Vec<MerklePriceUpdate>,
tree: MerkleTree<Keccak160>,
emitter_address: [u8; 32],
emitter_chain: u16,
) -> Vec<u8> {
let mut root_hash = [0u8; 20];
root_hash.copy_from_slice(&to_vec::<_, BigEndian>(&tree.root).unwrap()[..20]);
let wormhole_message = WormholeMessage::new(WormholePayload::Merkle(WormholeMerkleRoot {
slot: 0,
ring_size: 0,
root: root_hash,
}));
let vaa = wormhole_sdk::Vaa {
emitter_chain: emitter_chain.into(),
emitter_address: wormhole_sdk::Address(emitter_address),
sequence: 2,
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(&to_vec::<_, BigEndian>(&wormhole_message).unwrap())
.expect("Failed to write Payload");
cur.into_inner()
};
let accumulator_update_data = AccumulatorUpdateData::new(Proof::WormholeMerkle {
vaa: PrefixedVec::from(vaa),
updates: price_updates,
});
to_vec::<_, BigEndian>(&accumulator_update_data).unwrap()
}
fn create_accumulator_message(all_feeds: &[Message], updates: &[Message]) -> Vec<u8> {
let all_feeds_bytes: Vec<_> = all_feeds
.iter()
.map(|f| to_vec::<_, BigEndian>(f).unwrap())
.collect();
let all_feeds_bytes_refs: Vec<_> = all_feeds_bytes.iter().map(|f| f.as_ref()).collect();
let tree = MerkleTree::<Keccak160>::new(all_feeds_bytes_refs.as_slice()).unwrap();
let mut price_updates: Vec<MerklePriceUpdate> = vec![];
for update in updates {
let proof = tree
.prove(&to_vec::<_, BigEndian>(update).unwrap())
.unwrap();
price_updates.push(MerklePriceUpdate {
message: PrefixedVec::from(to_vec::<_, BigEndian>(update).unwrap()),
proof,
});
}
create_accumulator_message_from_updates(
price_updates,
tree,
[1; 32],
wormhole_sdk::Chain::Any.into(),
)
}
let (_, contract, _) = initialize_chain().await;
// Submit a new Source to the contract, this will trigger a cross-contract call to wormhole
let vaa = wormhole_sdk::Vaa {
emitter_chain: wormhole_sdk::Chain::Any,
emitter_address: wormhole_sdk::Address([0; 32]),
sequence: 1,
payload: (),
..Default::default()
};
let vaa = {
let mut cur = Cursor::new(Vec::new());
serde_wormhole::to_writer(&mut cur, &vaa).expect("Failed to serialize VAA");
cur.write_all(
&GovernanceInstruction {
target: Chain::from(WormholeChain::Any),
module: GovernanceModule::Target,
action: GovernanceAction::SetDataSources {
data_sources: vec![
Source::default(),
Source {
emitter: [1; 32],
chain: Chain::from(WormholeChain::Any),
},
],
},
}
.serialize()
.unwrap(),
)
.expect("Failed to write Payload");
hex::encode(cur.into_inner())
};
assert!(contract
.call("execute_governance_instruction")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"vaa": vaa,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Create a couple of test feeds.
let feed_1 = create_dummy_price_feed_message(100);
let feed_2 = create_dummy_price_feed_message(200);
let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1]);
let message = hex::encode(message);
// Call the usual UpdatePriceFeed function.
assert!(contract
.call("update_price_feeds")
.gas(300_000_000_000_000)
.deposit(300_000_000_000_000_000_000_000)
.args_json(&json!({
"data": message,
}))
.transact_async()
.await
.expect("Failed to submit VAA")
.await
.unwrap()
.failures()
.is_empty());
// Check the price feed actually updated. Check both types of serialized PriceIdentifier.
let mut identifier = [0; 32];
identifier[0] = 100;
assert_eq!(
Some(Price {
price: 100.into(),
conf: 100.into(),
expo: 100,
publish_time: 100,
}),
serde_json::from_slice::<Option<Price>>(
&contract
.view("get_price_unsafe")
.args_json(&json!({ "price_identifier": PriceIdentifier(identifier) }))
.await
.unwrap()
.result
)
.unwrap(),
);
}
#[tokio::test]
async fn test_sdk_compat() {
let price = pyth_sdk::Price {
price: i64::MAX,
conf: u64::MAX,
expo: 100,
publish_time: 100,
};
let encoded = serde_json::to_string(&price).unwrap();
let decoded_price: Price = serde_json::from_str(&encoded).unwrap();
assert_eq!(
decoded_price,
Price {
price: i64::MAX.into(),
conf: u64::MAX.into(),
expo: 100,
publish_time: 100,
}
);
}
#[tokio::test]
async fn test_borsh_field_cmopat() {
use near_sdk::borsh::{
self,
BorshDeserialize,
BorshSerialize,
};
let price = pyth_sdk::Price {
price: i64::MAX,
conf: u64::MAX,
expo: 100,
publish_time: 100,
};
// Verify that we can still BorshDeserialize a struct with a different field name. Confirms
// we don't have to migrate the state.
#[derive(Eq, PartialEq, Debug, BorshSerialize, BorshDeserialize)]
struct PriceTester {
price: i64,
conf: u64,
expo: u32,
bad_field_name: u64,
}
let encoded = near_sdk::borsh::BorshSerialize::try_to_vec(&price).unwrap();
let decoded_price: PriceTester =
near_sdk::borsh::BorshDeserialize::try_from_slice(&encoded).unwrap();
assert_eq!(
decoded_price,
PriceTester {
price: i64::MAX.into(),
conf: u64::MAX.into(),
expo: 100,
bad_field_name: 100,
}
);
}