Skip to content

Commit f1b6aee

Browse files
committed
feat(lazer): add sui example
1 parent 4641c36 commit f1b6aee

File tree

6 files changed

+656
-0
lines changed

6 files changed

+656
-0
lines changed

lazer/sui/Move.lock

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# @generated by Move, please check-in and do not edit manually.
2+
3+
[move]
4+
version = 3
5+
manifest_digest = "9A25A20E6E3BABDD1C296A4B0A45AE53C1ED219ECDBEDB8ABFE47CE82D7EB785"
6+
deps_digest = "F9B494B64F0615AED0E98FC12A85B85ECD2BC5185C22D30E7F67786BB52E507C"
7+
dependencies = [
8+
{ id = "Bridge", name = "Bridge" },
9+
{ id = "MoveStdlib", name = "MoveStdlib" },
10+
{ id = "Sui", name = "Sui" },
11+
{ id = "SuiSystem", name = "SuiSystem" },
12+
]
13+
14+
[[move.package]]
15+
id = "Bridge"
16+
source = { git = "https://github.com/MystenLabs/sui.git", rev = "209f0da8e316", subdir = "crates/sui-framework/packages/bridge" }
17+
18+
dependencies = [
19+
{ id = "MoveStdlib", name = "MoveStdlib" },
20+
{ id = "Sui", name = "Sui" },
21+
{ id = "SuiSystem", name = "SuiSystem" },
22+
]
23+
24+
[[move.package]]
25+
id = "MoveStdlib"
26+
source = { git = "https://github.com/MystenLabs/sui.git", rev = "209f0da8e316", subdir = "crates/sui-framework/packages/move-stdlib" }
27+
28+
[[move.package]]
29+
id = "Sui"
30+
source = { git = "https://github.com/MystenLabs/sui.git", rev = "209f0da8e316", subdir = "crates/sui-framework/packages/sui-framework" }
31+
32+
dependencies = [
33+
{ id = "MoveStdlib", name = "MoveStdlib" },
34+
]
35+
36+
[[move.package]]
37+
id = "SuiSystem"
38+
source = { git = "https://github.com/MystenLabs/sui.git", rev = "209f0da8e316", subdir = "crates/sui-framework/packages/sui-system" }
39+
40+
dependencies = [
41+
{ id = "MoveStdlib", name = "MoveStdlib" },
42+
{ id = "Sui", name = "Sui" },
43+
]
44+
45+
[move.toolchain-version]
46+
compiler-version = "1.52.2"
47+
edition = "2024.beta"
48+
flavor = "sui"

lazer/sui/Move.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "lazer_example"
3+
edition = "2024.beta"
4+
5+
[dependencies]
6+
7+
[addresses]
8+
lazer_example = "0x0"
9+
10+
[dev-dependencies]
11+
12+
[dev-addresses]

lazer/sui/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Pyth Lazer Sui Implementation
2+
3+
**⚠️ DISCLAIMER: This is an example implementation for demonstration purposes only. It has not been audited and should be used at your own risk. Do not use this code in production without proper security review and testing.**
4+
5+
A Sui Move implementation example for parsing and validating [Pyth Lazer](https://docs.pyth.network/lazer) price feed updates. This project demonstrates on-chain verification and parsing of cryptographically signed price feed data from the Pyth Network's high-frequency Lazer protocol. Look at the [`lazer_example` module](./sources/lazer_example.move) for the main implementation.
6+
7+
## Prerequisites
8+
9+
- [Sui CLI](https://docs.sui.io/guides/developer/getting-started/sui-install) installed
10+
- Basic familiarity with Move programming language
11+
12+
## Building and Testing the Project
13+
14+
1. **Build the project**:
15+
```bash
16+
sui move build
17+
```
18+
19+
2. **Run all tests**:
20+
```bash
21+
sui move test
22+
```
23+
24+
**Run specific test**:
25+
```bash
26+
sui move test test_parse_and_validate_update
27+
```
28+
29+
## Important Notes
30+
- The `parse_and_validate_update` function uses a single hardcoded public key for signature verification. However, in a real-world scenario, the set of valid public keys may change over time, and multiple keys might be required. For production use, store the authorized public keys in the contract's configuration storage and reference them dynamically, rather than relying on a hardcoded value.
31+
- There is no proper error handling in the `parse_and_validate_update` function and all the assertions use the same error code (0).

lazer/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 lazer_example::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+
}

lazer/sui/sources/i64.move

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/// Adopted from pyth::i64
2+
3+
module lazer_example::i64;
4+
5+
const MAX_POSITIVE_MAGNITUDE: u64 = (1 << 63) - 1;
6+
const MAX_NEGATIVE_MAGNITUDE: u64 = (1 << 63);
7+
8+
/// To consume these values, first call `get_is_negative()` to determine if the I64
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 I64 has copy, drop, store {
13+
negative: bool,
14+
magnitude: u64,
15+
}
16+
17+
public fun new(magnitude: u64, mut negative: bool): I64 {
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+
25+
// Ensure we have a single zero representation: (0, false).
26+
// (0, true) is invalid.
27+
if (magnitude == 0) {
28+
negative = false;
29+
};
30+
31+
I64 {
32+
magnitude,
33+
negative,
34+
}
35+
}
36+
37+
public fun get_is_negative(i: &I64): bool {
38+
i.negative
39+
}
40+
41+
public fun get_magnitude_if_positive(in: &I64): u64 {
42+
assert!(!in.negative, 0); // error::negative_value()
43+
in.magnitude
44+
}
45+
46+
public fun get_magnitude_if_negative(in: &I64): u64 {
47+
assert!(in.negative, 0); //error::positive_value()
48+
in.magnitude
49+
}
50+
51+
public fun from_u64(from: u64): I64 {
52+
// Use the MSB to determine whether the number is negative or not.
53+
let negative = (from >> 63) == 1;
54+
let magnitude = parse_magnitude(from, negative);
55+
56+
new(magnitude, negative)
57+
}
58+
59+
fun parse_magnitude(from: u64, negative: bool): u64 {
60+
// If positive, then return the input verbatamin
61+
if (!negative) {
62+
return from
63+
};
64+
65+
// Otherwise convert from two's complement by inverting and adding 1
66+
let inverted = from ^ 0xFFFFFFFFFFFFFFFF;
67+
inverted + 1
68+
}
69+
70+
#[test]
71+
fun test_max_positive_magnitude() {
72+
new(0x7FFFFFFFFFFFFFFF, false);
73+
assert!(&new(1<<63 - 1, false) == &from_u64(1<<63 - 1), 1);
74+
}
75+
76+
#[test]
77+
#[expected_failure]
78+
fun test_magnitude_too_large_positive() {
79+
new(0x8000000000000000, false);
80+
}
81+
82+
#[test]
83+
fun test_max_negative_magnitude() {
84+
new(0x8000000000000000, true);
85+
assert!(&new(1<<63, true) == &from_u64(1<<63), 1);
86+
}
87+
88+
#[test]
89+
#[expected_failure]
90+
fun test_magnitude_too_large_negative() {
91+
new(0x8000000000000001, true);
92+
}
93+
94+
#[test]
95+
fun test_from_u64_positive() {
96+
assert!(from_u64(0x64673) == new(0x64673, false), 1);
97+
}
98+
99+
#[test]
100+
fun test_from_u64_negative() {
101+
assert!(from_u64(0xFFFFFFFFFFFEDC73) == new(0x1238D, true), 1);
102+
}
103+
104+
#[test]
105+
fun test_get_is_negative() {
106+
assert!(get_is_negative(&new(234, true)) == true, 1);
107+
assert!(get_is_negative(&new(767, false)) == false, 1);
108+
}
109+
110+
#[test]
111+
fun test_get_magnitude_if_positive_positive() {
112+
assert!(get_magnitude_if_positive(&new(7686, false)) == 7686, 1);
113+
}
114+
115+
#[test]
116+
#[expected_failure]
117+
fun test_get_magnitude_if_positive_negative() {
118+
assert!(get_magnitude_if_positive(&new(7686, true)) == 7686, 1);
119+
}
120+
121+
#[test]
122+
fun test_get_magnitude_if_negative_negative() {
123+
assert!(get_magnitude_if_negative(&new(7686, true)) == 7686, 1);
124+
}
125+
126+
#[test]
127+
#[expected_failure]
128+
fun test_get_magnitude_if_negative_positive() {
129+
assert!(get_magnitude_if_negative(&new(7686, false)) == 7686, 1);
130+
}
131+
132+
#[test]
133+
fun test_single_zero_representation() {
134+
assert!(&new(0, true) == &new(0, false), 1);
135+
assert!(&new(0, true) == &from_u64(0), 1);
136+
assert!(&new(0, false) == &from_u64(0), 1);
137+
}

0 commit comments

Comments
 (0)