Skip to content

Commit 00f8405

Browse files
committed
feat(lazer/sui): init structs and basic example
1 parent f0105d2 commit 00f8405

File tree

8 files changed

+694
-28
lines changed

8 files changed

+694
-28
lines changed

lazer/contracts/sui/Move.toml

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,12 @@
11
[package]
22
name = "pyth_lazer"
33
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
4-
# license = "" # e.g., "MIT", "GPL", "Apache 2.0"
5-
# authors = ["..."] # e.g., ["Joe Smith ([email protected])", "John Snow ([email protected])"]
64

75
[dependencies]
86

9-
# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`.
10-
# Revision can be a branch, a tag, and a commit hash.
11-
# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" }
12-
13-
# For local dependencies use `local = path`. Path is relative to the package root
14-
# Local = { local = "../path/to" }
15-
16-
# To resolve a version conflict and force a specific version for dependency
17-
# override use `override = true`
18-
# Override = { local = "../conflicting/version", override = true }
19-
207
[addresses]
218
pyth_lazer = "0x0"
229

23-
# Named addresses will be accessible in Move as `@name`. They're also exported:
24-
# for example, `std = "0x1"` is exported by the Standard Library.
25-
# alice = "0xA11CE"
26-
2710
[dev-dependencies]
28-
# The dev-dependencies section allows overriding dependencies for `--test` and
29-
# `--dev` modes. You can introduce test-only dependencies here.
30-
# Local = { local = "../path/to/dev-build" }
3111

3212
[dev-addresses]
33-
# The dev-addresses section allows overwriting named addresses for the `--test`
34-
# and `--dev` modes.
35-
# alice = "0xB0B"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module pyth_lazer::channel;
2+
3+
public enum Channel has copy, drop {
4+
Invalid,
5+
RealTime,
6+
FixedRate50ms,
7+
FixedRate200ms,
8+
}
9+
10+
/// Check if the channel is Invalid
11+
public fun is_invalid(channel: &Channel): bool {
12+
match (channel) {
13+
Channel::Invalid => true,
14+
_ => false,
15+
}
16+
}
17+
18+
/// Check if the channel is RealTime
19+
public fun is_real_time(channel: &Channel): bool {
20+
match (channel) {
21+
Channel::RealTime => true,
22+
_ => false,
23+
}
24+
}
25+
26+
/// Check if the channel is FixedRate50ms
27+
public fun is_fixed_rate_50ms(channel: &Channel): bool {
28+
match (channel) {
29+
Channel::FixedRate50ms => true,
30+
_ => false,
31+
}
32+
}
33+
34+
/// Check if the channel is FixedRate200ms
35+
public fun is_fixed_rate_200ms(channel: &Channel): bool {
36+
match (channel) {
37+
Channel::FixedRate200ms => true,
38+
_ => false,
39+
}
40+
}
41+
42+
/// Get the update interval in milliseconds for fixed rate channels, returns 0 for non-fixed rate channels
43+
public fun get_update_interval_ms(channel: &Channel): u64 {
44+
match (channel) {
45+
Channel::FixedRate50ms => 50,
46+
Channel::FixedRate200ms => 200,
47+
_ => 0,
48+
}
49+
}

lazer/contracts/sui/sources/feed.move

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
module pyth_lazer::feed;
2+
3+
use pyth_lazer::i16::I16;
4+
use pyth_lazer::i64::I64;
5+
6+
/// The feed struct is based on the Lazer rust protocol definition defined here:
7+
/// https://github.com/pyth-network/pyth-crosschain/blob/main/lazer/sdk/rust/protocol/src/types.rs#L10
8+
///
9+
/// Some fields in Lazer are optional, as in Lazer might return None for them due to some conditions (for example,
10+
/// not having enough publishers to calculate the price) and that is why they are represented as Option<Option<T>>.
11+
/// The first Option<T> is for the existence of the field within the update data and the second Option<T> is for the
12+
/// value of the field.
13+
public struct Feed has copy, drop {
14+
/// Unique identifier for the price feed (e.g., 1 for BTC/USD, 2 for ETH/USD)
15+
feed_id: u32,
16+
/// Current aggregate price from all publishers
17+
price: Option<Option<I64>>,
18+
/// Best bid price available across all publishers
19+
best_bid_price: Option<Option<I64>>,
20+
/// Best ask price available across all publishers
21+
best_ask_price: Option<Option<I64>>,
22+
/// Number of publishers contributing to this price feed
23+
publisher_count: Option<u16>,
24+
/// Price exponent (typically negative, e.g., -8 means divide price by 10^8)
25+
exponent: Option<I16>,
26+
/// Confidence interval representing price uncertainty
27+
confidence: Option<Option<I64>>,
28+
/// Funding rate for derivative products (e.g., perpetual futures)
29+
funding_rate: Option<Option<I64>>,
30+
/// Timestamp when the funding rate was last updated
31+
funding_timestamp: Option<Option<u64>>,
32+
}
33+
34+
/// Get the feed ID
35+
public fun feed_id(feed: &Feed): u32 {
36+
feed.feed_id
37+
}
38+
39+
/// Get the price
40+
public fun price(feed: &Feed): Option<Option<I64>> {
41+
feed.price
42+
}
43+
44+
/// Get the best bid price
45+
public fun best_bid_price(feed: &Feed): Option<Option<I64>> {
46+
feed.best_bid_price
47+
}
48+
49+
/// Get the best ask price
50+
public fun best_ask_price(feed: &Feed): Option<Option<I64>> {
51+
feed.best_ask_price
52+
}
53+
54+
/// Get the publisher count
55+
public fun publisher_count(feed: &Feed): Option<u16> {
56+
feed.publisher_count
57+
}
58+
59+
/// Get the exponent
60+
public fun exponent(feed: &Feed): Option<I16> {
61+
feed.exponent
62+
}
63+
64+
/// Get the confidence interval
65+
public fun confidence(feed: &Feed): Option<Option<I64>> {
66+
feed.confidence
67+
}
68+
69+
/// Get the funding rate
70+
public fun funding_rate(feed: &Feed): Option<Option<I64>> {
71+
feed.funding_rate
72+
}
73+
74+
/// Get the funding timestamp
75+
public fun funding_timestamp(feed: &Feed): Option<Option<u64>> {
76+
feed.funding_timestamp
77+
}

lazer/contracts/sui/sources/i16.move

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/// Adopted from pyth::i64, adapted for i16
2+
3+
module pyth_lazer::i16;
4+
5+
const MAX_POSITIVE_MAGNITUDE: u64 = (1 << 15) - 1; // 32767
6+
const MAX_NEGATIVE_MAGNITUDE: u64 = (1 << 15); // 32768
7+
8+
/// To consume these values, first call `get_is_negative()` to determine if the I16
9+
/// represents a negative or positive value. Then call `get_magnitude_if_positive()` or
10+
/// `get_magnitude_if_negative()` to get the magnitude of the number in unsigned u64 format.
11+
/// This API forces consumers to handle positive and negative numbers safely.
12+
public struct I16 has copy, drop, store {
13+
negative: bool,
14+
magnitude: u64,
15+
}
16+
17+
public fun new(magnitude: u64, mut negative: bool): I16 {
18+
let mut max_magnitude = MAX_POSITIVE_MAGNITUDE;
19+
if (negative) {
20+
max_magnitude = MAX_NEGATIVE_MAGNITUDE;
21+
};
22+
assert!(magnitude <= max_magnitude, 0); //error::magnitude_too_large()
23+
24+
// Ensure we have a single zero representation: (0, false).
25+
// (0, true) is invalid.
26+
if (magnitude == 0) {
27+
negative = false;
28+
};
29+
30+
I16 {
31+
magnitude,
32+
negative,
33+
}
34+
}
35+
36+
public fun get_is_negative(i: &I16): bool {
37+
i.negative
38+
}
39+
40+
public fun get_magnitude_if_positive(in: &I16): u64 {
41+
assert!(!in.negative, 0); // error::negative_value()
42+
in.magnitude
43+
}
44+
45+
public fun get_magnitude_if_negative(in: &I16): u64 {
46+
assert!(in.negative, 0); //error::positive_value()
47+
in.magnitude
48+
}
49+
50+
public fun from_u16(from: u16): I16 {
51+
// Use the MSB to determine whether the number is negative or not.
52+
let from_u64 = (from as u64);
53+
let negative = (from_u64 >> 15) == 1;
54+
let magnitude = parse_magnitude(from_u64, negative);
55+
56+
new(magnitude, negative)
57+
}
58+
59+
fun parse_magnitude(from: u64, negative: bool): u64 {
60+
// If positive, then return the input verbatim
61+
if (!negative) {
62+
return from
63+
};
64+
65+
// Otherwise convert from two's complement by inverting and adding 1
66+
// For 16-bit numbers, we only invert the lower 16 bits
67+
let inverted = from ^ 0xFFFF;
68+
inverted + 1
69+
}
70+
71+
#[test]
72+
fun test_max_positive_magnitude() {
73+
new(0x7FFF, false); // 32767
74+
assert!(&new((1<<15) - 1, false) == &from_u16(((1<<15) - 1) as u16), 1);
75+
}
76+
77+
#[test]
78+
#[expected_failure]
79+
fun test_magnitude_too_large_positive() {
80+
new(0x8000, false); // 32768
81+
}
82+
83+
#[test]
84+
fun test_max_negative_magnitude() {
85+
new(0x8000, true); // 32768
86+
assert!(&new(1<<15, true) == &from_u16((1<<15) as u16), 1);
87+
}
88+
89+
#[test]
90+
#[expected_failure]
91+
fun test_magnitude_too_large_negative() {
92+
new(0x8001, true); // 32769
93+
}
94+
95+
#[test]
96+
fun test_from_u16_positive() {
97+
assert!(from_u16(0x1234) == new(0x1234, false), 1);
98+
}
99+
100+
#[test]
101+
fun test_from_u16_negative() {
102+
assert!(from_u16(0xEDCC) == new(0x1234, true), 1);
103+
}
104+
105+
#[test]
106+
fun test_get_is_negative() {
107+
assert!(get_is_negative(&new(234, true)) == true, 1);
108+
assert!(get_is_negative(&new(767, false)) == false, 1);
109+
}
110+
111+
#[test]
112+
fun test_get_magnitude_if_positive_positive() {
113+
assert!(get_magnitude_if_positive(&new(7686, false)) == 7686, 1);
114+
}
115+
116+
#[test]
117+
#[expected_failure]
118+
fun test_get_magnitude_if_positive_negative() {
119+
assert!(get_magnitude_if_positive(&new(7686, true)) == 7686, 1);
120+
}
121+
122+
#[test]
123+
fun test_get_magnitude_if_negative_negative() {
124+
assert!(get_magnitude_if_negative(&new(7686, true)) == 7686, 1);
125+
}
126+
127+
#[test]
128+
#[expected_failure]
129+
fun test_get_magnitude_if_negative_positive() {
130+
assert!(get_magnitude_if_negative(&new(7686, false)) == 7686, 1);
131+
}
132+
133+
#[test]
134+
fun test_single_zero_representation() {
135+
assert!(&new(0, true) == &new(0, false), 1);
136+
assert!(&new(0, true) == &from_u16(0), 1);
137+
assert!(&new(0, false) == &from_u16(0), 1);
138+
}
139+
140+
#[test]
141+
fun test_boundary_values() {
142+
// Test positive boundary
143+
assert!(from_u16(0x7FFF) == new(32767, false), 1);
144+
145+
// Test negative boundary
146+
assert!(from_u16(0x8000) == new(32768, true), 1);
147+
148+
// Test -1
149+
assert!(from_u16(0xFFFF) == new(1, true), 1);
150+
}

0 commit comments

Comments
 (0)