Skip to content

Commit 6ff5016

Browse files
authored
feat(lazer): add sui example (#59)
* feat(lazer): add sui example * chore: add ci * chore: fix solana ci * fix: address comments
1 parent 4641c36 commit 6ff5016

File tree

9 files changed

+703
-9
lines changed

9 files changed

+703
-9
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Lazer Sui Move Build and Test
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
defaults:
12+
run:
13+
working-directory: lazer/sui
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Install Sui CLI
20+
run: |
21+
LATEST_RELEASE=$(curl -s https://api.github.com/repos/MystenLabs/sui/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
22+
echo "Installing Sui CLI version: $LATEST_RELEASE"
23+
24+
wget -q "https://github.com/MystenLabs/sui/releases/download/$LATEST_RELEASE/sui-$LATEST_RELEASE-ubuntu-x86_64.tgz"
25+
26+
tar -xzf "sui-$LATEST_RELEASE-ubuntu-x86_64.tgz"
27+
chmod +x sui
28+
sudo mv sui /usr/local/bin/
29+
30+
sui --version
31+
32+
- name: Build Sui Move contract
33+
run: sui move build
34+
35+
- name: Run Sui Move tests
36+
run: sui move test
37+
38+
- name: Test with verbose output
39+
run: sui move test --gas-limit 100000000

lazer/solana/tests/test1.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,11 @@ async fn test1() {
2121
env::set_var(
2222
"SBF_OUT_DIR",
2323
format!(
24-
"{}/target/sbf-solana-solana/release",
24+
"{}/target/sbpf-solana-solana/release",
2525
env::var("CARGO_MANIFEST_DIR").unwrap()
2626
),
2727
);
2828
}
29-
std::fs::copy(
30-
"tests/pyth_lazer_solana_contract.so",
31-
format!(
32-
"{}/target/sbf-solana-solana/release/pyth_lazer_solana_contract.so",
33-
env::var("CARGO_MANIFEST_DIR").unwrap()
34-
),
35-
)
36-
.unwrap();
3729
println!("if add_program fails, run `cargo build-sbf` first.");
3830
let mut program_test = ProgramTest::new(
3931
"pyth_lazer_solana_example",

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+
}

0 commit comments

Comments
 (0)