Skip to content

Commit 2b118c1

Browse files
committed
feat(near): parse string identifiers
1 parent 2c93737 commit 2b118c1

File tree

4 files changed

+87
-18
lines changed

4 files changed

+87
-18
lines changed

target_chains/near/receiver/src/ext.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,15 @@ pub trait Wormhole {
2828
/// An external definition of the Pyth interface.
2929
#[ext_contract(ext_pyth)]
3030
pub trait Pyth {
31+
// See the implementation for details. The `data` parameter can be found by using a Hermes
32+
// price feed endpoint, and should be fed in as base64.
3133
fn update_price_feeds(&mut self, data: String) -> Result<(), Error>;
3234
fn get_update_fee_estimate(&self, vaa: String) -> U128;
3335
fn get_sources(&self) -> Vec<Source>;
3436
fn get_stale_threshold(&self) -> u64;
37+
38+
// See implementations for details, PriceIdentifier can be passed either as a 64 character
39+
// hex price ID which can be found on the Pyth homepage.
3540
fn price_feed_exists(&self, price_identifier: PriceIdentifier) -> bool;
3641
fn get_price(&self, price_identifier: PriceIdentifier) -> Option<Price>;
3742
fn get_price_unsafe(&self, price_identifier: PriceIdentifier) -> Option<Price>;

target_chains/near/receiver/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ impl Pyth {
151151
/// Instruction for processing VAA's relayed via Wormhole.
152152
///
153153
/// Note that VAA verification requires calling Wormhole so processing of the VAA itself is
154-
/// done in a callback handler, see `process_vaa_callback`.
154+
/// done in a callback handler, see `process_vaa_callback`. The `data` parameter can be
155+
/// retrieved from Hermes using the price feed APIs.
155156
#[payable]
156157
#[handle_result]
157158
pub fn update_price_feeds(&mut self, data: String) -> Result<(), Error> {

target_chains/near/receiver/src/state.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,58 @@ pub type WormholeSignature = [u8; 65];
2121
/// Type alias for Wormhole's cross-chain 32-byte address.
2222
pub type WormholeAddress = [u8; 32];
2323

24-
#[derive(BorshDeserialize, BorshSerialize, Deserialize, Serialize)]
25-
#[serde(crate = "near_sdk::serde")]
24+
#[derive(BorshDeserialize, BorshSerialize)]
2625
#[repr(transparent)]
2726
pub struct PriceIdentifier(pub [u8; 32]);
2827

28+
impl<'de> near_sdk::serde::Deserialize<'de> for PriceIdentifier {
29+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
30+
where
31+
D: near_sdk::serde::Deserializer<'de>,
32+
{
33+
/// A visitor that deserializes a hex string into a 32 byte array.
34+
struct IdentifierVisitor;
35+
36+
impl<'de> near_sdk::serde::de::Visitor<'de> for IdentifierVisitor {
37+
/// Target type for either a hex string or a 32 byte array.
38+
type Value = [u8; 32];
39+
40+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
41+
formatter.write_str("a hex string")
42+
}
43+
44+
// When given a string, attempt a standard hex decode.
45+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
46+
where
47+
E: near_sdk::serde::de::Error,
48+
{
49+
if value.len() != 64 {
50+
return Err(E::custom(format!(
51+
"expected a 64 character hex string, got {}",
52+
value.len()
53+
)));
54+
}
55+
let mut bytes = [0u8; 32];
56+
hex::decode_to_slice(value, &mut bytes).map_err(E::custom)?;
57+
Ok(bytes)
58+
}
59+
}
60+
61+
deserializer
62+
.deserialize_any(IdentifierVisitor)
63+
.map(PriceIdentifier)
64+
}
65+
}
66+
67+
impl near_sdk::serde::Serialize for PriceIdentifier {
68+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69+
where
70+
S: near_sdk::serde::Serializer,
71+
{
72+
serializer.serialize_str(&hex::encode(&self.0))
73+
}
74+
}
75+
2976
/// A price with a degree of uncertainty, represented as a price +- a confidence interval.
3077
///
3178
/// The confidence interval roughly corresponds to the standard error of a normal distribution.

target_chains/near/receiver/tests/workspaces.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ async fn test_stale_threshold() {
400400
&contract
401401
.view("get_update_fee_estimate")
402402
.args_json(&json!({
403-
"vaa": vaa,
403+
"data": vaa,
404404
}))
405405
.await
406406
.unwrap()
@@ -410,7 +410,7 @@ async fn test_stale_threshold() {
410410

411411
// Submit price. As there are no prices this should succeed despite being old.
412412
assert!(contract
413-
.call("update_price_feed")
413+
.call("update_price_feeds")
414414
.gas(300_000_000_000_000)
415415
.deposit(update_fee.into())
416416
.args_json(&json!({
@@ -482,7 +482,7 @@ async fn test_stale_threshold() {
482482

483483
// The update handler should now succeed even if price is old, but simply not update the price.
484484
assert!(contract
485-
.call("update_price_feed")
485+
.call("update_price_feeds")
486486
.gas(300_000_000_000_000)
487487
.deposit(update_fee.into())
488488
.args_json(&json!({
@@ -616,7 +616,7 @@ async fn test_contract_fees() {
616616
&contract
617617
.view("get_update_fee_estimate")
618618
.args_json(&json!({
619-
"vaa": vaa,
619+
"data": vaa,
620620
}))
621621
.await
622622
.unwrap()
@@ -648,7 +648,7 @@ async fn test_contract_fees() {
648648
&contract
649649
.view("get_update_fee_estimate")
650650
.args_json(&json!({
651-
"vaa": vaa,
651+
"data": vaa,
652652
}))
653653
.await
654654
.unwrap()
@@ -699,7 +699,7 @@ async fn test_contract_fees() {
699699
};
700700

701701
assert!(contract
702-
.call("update_price_feed")
702+
.call("update_price_feeds")
703703
.gas(300_000_000_000_000)
704704
.deposit(update_fee.into())
705705
.args_json(&json!({
@@ -990,7 +990,6 @@ async fn test_accumulator_updates() {
990990
fn create_accumulator_message_from_updates(
991991
price_updates: Vec<MerklePriceUpdate>,
992992
tree: MerkleTree<Keccak160>,
993-
corrupt_wormhole_message: bool,
994993
emitter_address: [u8; 32],
995994
emitter_chain: u16,
996995
) -> Vec<u8> {
@@ -1026,11 +1025,7 @@ async fn test_accumulator_updates() {
10261025
to_vec::<_, BigEndian>(&accumulator_update_data).unwrap()
10271026
}
10281027

1029-
fn create_accumulator_message(
1030-
all_feeds: &[Message],
1031-
updates: &[Message],
1032-
corrupt_wormhole_message: bool,
1033-
) -> Vec<u8> {
1028+
fn create_accumulator_message(all_feeds: &[Message], updates: &[Message]) -> Vec<u8> {
10341029
let all_feeds_bytes: Vec<_> = all_feeds
10351030
.iter()
10361031
.map(|f| to_vec::<_, BigEndian>(f).unwrap())
@@ -1050,7 +1045,6 @@ async fn test_accumulator_updates() {
10501045
create_accumulator_message_from_updates(
10511046
price_updates,
10521047
tree,
1053-
corrupt_wormhole_message,
10541048
[1; 32],
10551049
wormhole::Chain::Any.into(),
10561050
)
@@ -1109,12 +1103,12 @@ async fn test_accumulator_updates() {
11091103
// Create a couple of test feeds.
11101104
let feed_1 = create_dummy_price_feed_message(100);
11111105
let feed_2 = create_dummy_price_feed_message(200);
1112-
let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1], false);
1106+
let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1]);
11131107
let message = hex::encode(message);
11141108

11151109
// Call the usual UpdatePriceFeed function.
11161110
assert!(contract
1117-
.call("update_price_feed")
1111+
.call("update_price_feeds")
11181112
.gas(300_000_000_000_000)
11191113
.deposit(300_000_000_000_000_000_000_000)
11201114
.args_json(&json!({
@@ -1127,4 +1121,26 @@ async fn test_accumulator_updates() {
11271121
.unwrap()
11281122
.failures()
11291123
.is_empty());
1124+
1125+
// Check the price feed actually updated. Check both types of serialized PriceIdentifier.
1126+
let mut identifier = [0; 32];
1127+
identifier[0] = 100;
1128+
1129+
assert_eq!(
1130+
Some(Price {
1131+
price: 100,
1132+
conf: 100,
1133+
expo: 100,
1134+
timestamp: 100,
1135+
}),
1136+
serde_json::from_slice::<Option<Price>>(
1137+
&contract
1138+
.view("get_price_unsafe")
1139+
.args_json(&json!({ "price_identifier": PriceIdentifier(identifier) }))
1140+
.await
1141+
.unwrap()
1142+
.result
1143+
)
1144+
.unwrap(),
1145+
);
11301146
}

0 commit comments

Comments
 (0)