Skip to content

Commit 4656faf

Browse files
feat(stylus): complete governance_structs.rs implementation
- Translate governance logic from StarkNet to Stylus/Rust patterns - Implement parse_instruction function with proper byte parsing - Add governance-specific error types to PythReceiverError enum - Replace StarkNet types (ContractAddress, ByteBuffer) with Stylus equivalents - Use Rust idiomatic patterns for data structures and error handling - Support all governance actions: UpgradeContract, SetDataSources, SetFee, etc. - Add proper imports and module structure for governance functionality Fixes compilation errors and provides complete governance parsing capability for the Stylus implementation following Rust best practices. Co-Authored-By: [email protected] <[email protected]>
1 parent 0965156 commit 4656faf

File tree

3 files changed

+240
-52
lines changed

3 files changed

+240
-52
lines changed

target_chains/stylus/contracts/pyth-receiver/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub enum PythReceiverError {
2020
PriceFeedNotFoundWithinRange,
2121
NoFreshUpdate,
2222
PriceFeedNotFound,
23+
InvalidGovernanceMessage,
24+
InvalidGovernanceTarget,
25+
InvalidGovernanceAction,
2326
}
2427

2528
impl core::fmt::Debug for PythReceiverError {
@@ -49,6 +52,9 @@ impl From<PythReceiverError> for Vec<u8> {
4952
PythReceiverError::PriceFeedNotFoundWithinRange => 16,
5053
PythReceiverError::NoFreshUpdate => 17,
5154
PythReceiverError::PriceFeedNotFound => 18,
55+
PythReceiverError::InvalidGovernanceMessage => 19,
56+
PythReceiverError::InvalidGovernanceTarget => 20,
57+
PythReceiverError::InvalidGovernanceAction => 21,
5258
}]
5359
}
5460
}
Lines changed: 233 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
use alloy_primitives::Address;
2-
use ethers::abi::FixedBytes;
3-
use structs::DataSource;
1+
use alloc::vec::Vec;
2+
use stylus_sdk::alloy_primitives::{Address, FixedBytes, U16};
3+
use crate::structs::DataSource;
4+
use crate::error::PythReceiverError;
45

5-
#[derive(Drop, Copy, Debug, PartialEq, Serde, Hash)]
6+
const MAGIC: u32 = 0x5054474d;
7+
const MODULE_TARGET: u8 = 1;
8+
9+
#[derive(Clone, Debug, PartialEq)]
610
pub enum GovernanceAction {
711
UpgradeContract,
812
AuthorizeGovernanceDataSourceTransfer,
@@ -14,88 +18,265 @@ pub enum GovernanceAction {
1418
SetFeeInToken,
1519
}
1620

17-
impl U8TryIntoGovernanceAction of TryInto<u8, GovernanceAction> {
18-
fn try_into(self: u8) -> Option<GovernanceAction> {
19-
let v = match self {
20-
0 => GovernanceAction::UpgradeContract,
21-
1 => GovernanceAction::AuthorizeGovernanceDataSourceTransfer,
22-
2 => GovernanceAction::SetDataSources,
23-
3 => GovernanceAction::SetFee,
24-
4 => GovernanceAction::SetValidPeriod,
25-
5 => GovernanceAction::RequestGovernanceDataSourceTransfer,
26-
6 => GovernanceAction::SetWormholeAddress,
27-
7 => GovernanceAction::SetFeeInToken,
28-
_ => { return Option::None; },
29-
};
30-
Option::Some(v)
21+
impl TryFrom<u8> for GovernanceAction {
22+
type Error = PythReceiverError;
23+
24+
fn try_from(value: u8) -> Result<Self, Self::Error> {
25+
match value {
26+
0 => Ok(GovernanceAction::UpgradeContract),
27+
1 => Ok(GovernanceAction::AuthorizeGovernanceDataSourceTransfer),
28+
2 => Ok(GovernanceAction::SetDataSources),
29+
3 => Ok(GovernanceAction::SetFee),
30+
4 => Ok(GovernanceAction::SetValidPeriod),
31+
5 => Ok(GovernanceAction::RequestGovernanceDataSourceTransfer),
32+
6 => Ok(GovernanceAction::SetWormholeAddress),
33+
7 => Ok(GovernanceAction::SetFeeInToken),
34+
_ => Err(PythReceiverError::InvalidGovernanceAction),
35+
}
3136
}
3237
}
3338

34-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
39+
#[derive(Clone, Debug, PartialEq)]
3540
pub struct GovernanceInstruction {
3641
pub target_chain_id: u16,
3742
pub payload: GovernancePayload,
3843
}
3944

40-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
45+
#[derive(Clone, Debug, PartialEq)]
4146
pub enum GovernancePayload {
42-
UpgradeContract,
43-
AuthorizeGovernanceDataSourceTransfer,
44-
SetDataSources,
45-
SetFee,
46-
RequestGovernanceDataSourceTransfer,
47-
SetWormholeAddress,
48-
SetFeeInToken,
47+
UpgradeContract(UpgradeContract),
48+
AuthorizeGovernanceDataSourceTransfer(AuthorizeGovernanceDataSourceTransfer),
49+
SetDataSources(SetDataSources),
50+
SetFee(SetFee),
51+
RequestGovernanceDataSourceTransfer(RequestGovernanceDataSourceTransfer),
52+
SetWormholeAddress(SetWormholeAddress),
53+
SetFeeInToken(SetFeeInToken),
4954
}
5055

51-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
56+
#[derive(Clone, Debug, PartialEq)]
5257
pub struct SetFee {
5358
pub value: u64,
5459
pub expo: u64,
5560
}
5661

57-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
62+
#[derive(Clone, Debug, PartialEq)]
5863
pub struct SetFeeInToken {
5964
pub value: u64,
6065
pub expo: u64,
61-
pub token: ContractAddress,
66+
pub token: Address,
6267
}
6368

64-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
69+
#[derive(Clone, Debug, PartialEq)]
6570
pub struct SetDataSources {
66-
pub sources: Array<DataSource>,
71+
pub sources: Vec<DataSource>,
6772
}
6873

69-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
74+
#[derive(Clone, Debug, PartialEq)]
7075
pub struct SetWormholeAddress {
7176
pub address: Address,
7277
}
7378

74-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
79+
#[derive(Clone, Debug, PartialEq)]
7580
pub struct RequestGovernanceDataSourceTransfer {
76-
// Index is used to prevent replay attacks
77-
// So a claimVaa cannot be used twice.
7881
pub governance_data_source_index: u32,
7982
}
8083

81-
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
84+
#[derive(Clone, Debug, PartialEq)]
8285
pub struct AuthorizeGovernanceDataSourceTransfer {
83-
// Transfer governance control over this contract to another data source.
84-
// The claim_vaa field is a VAA created by the new data source; using a VAA prevents mistakes
85-
// in the handoff by ensuring that the new data source can send VAAs (i.e., is not an invalid
86-
// address).
87-
pub claim_vaa: FixedBytes<32>,
86+
pub claim_vaa: Vec<u8>,
8887
}
8988

90-
// #[derive(Drop, Clone, Debug, PartialEq, Serde)]
91-
// pub struct UpgradeContract {
92-
// // Class hash of the new contract class. The contract class must already be deployed on the
93-
// // network (e.g. with `starkli declare`). Class hash is a Poseidon hash of all properties
94-
// // of the contract code, including entry points, ABI, and bytecode,
95-
// // so specifying a hash securely identifies the new implementation.
96-
// pub new_implementation: ClassHash,
97-
// }
89+
#[derive(Clone, Debug, PartialEq)]
90+
pub struct UpgradeContract {
91+
pub new_implementation: FixedBytes<32>,
92+
}
9893

99-
pub fn parse_instruction(payload: Vec<u8>) -> GovernanceInstruction {
94+
pub fn parse_instruction(payload: Vec<u8>) -> Result<GovernanceInstruction, PythReceiverError> {
95+
if payload.len() < 8 {
96+
return Err(PythReceiverError::InvalidGovernanceMessage);
97+
}
10098

101-
}
99+
let mut cursor = 0;
100+
101+
let magic = u32::from_be_bytes([
102+
payload[cursor],
103+
payload[cursor + 1],
104+
payload[cursor + 2],
105+
payload[cursor + 3],
106+
]);
107+
cursor += 4;
108+
109+
if magic != MAGIC {
110+
return Err(PythReceiverError::InvalidGovernanceMessage);
111+
}
112+
113+
let module = payload[cursor];
114+
cursor += 1;
115+
116+
if module != MODULE_TARGET {
117+
return Err(PythReceiverError::InvalidGovernanceTarget);
118+
}
119+
120+
let action = GovernanceAction::try_from(payload[cursor])?;
121+
cursor += 1;
122+
123+
let target_chain_id = u16::from_be_bytes([payload[cursor], payload[cursor + 1]]);
124+
cursor += 2;
125+
126+
let governance_payload = match action {
127+
GovernanceAction::UpgradeContract => {
128+
if payload.len() < cursor + 32 {
129+
return Err(PythReceiverError::InvalidGovernanceMessage);
130+
}
131+
let mut new_implementation = [0u8; 32];
132+
new_implementation.copy_from_slice(&payload[cursor..cursor + 32]);
133+
cursor += 32;
134+
GovernancePayload::UpgradeContract(UpgradeContract {
135+
new_implementation: FixedBytes::from(new_implementation),
136+
})
137+
}
138+
GovernanceAction::AuthorizeGovernanceDataSourceTransfer => {
139+
let claim_vaa = payload[cursor..].to_vec();
140+
GovernancePayload::AuthorizeGovernanceDataSourceTransfer(
141+
AuthorizeGovernanceDataSourceTransfer { claim_vaa },
142+
)
143+
}
144+
GovernanceAction::RequestGovernanceDataSourceTransfer => {
145+
if payload.len() < cursor + 4 {
146+
return Err(PythReceiverError::InvalidGovernanceMessage);
147+
}
148+
let governance_data_source_index = u32::from_be_bytes([
149+
payload[cursor],
150+
payload[cursor + 1],
151+
payload[cursor + 2],
152+
payload[cursor + 3],
153+
]);
154+
cursor += 4;
155+
GovernancePayload::RequestGovernanceDataSourceTransfer(
156+
RequestGovernanceDataSourceTransfer {
157+
governance_data_source_index,
158+
},
159+
)
160+
}
161+
GovernanceAction::SetDataSources => {
162+
if payload.len() < cursor + 1 {
163+
return Err(PythReceiverError::InvalidGovernanceMessage);
164+
}
165+
let num_sources = payload[cursor];
166+
cursor += 1;
167+
168+
let mut sources = Vec::new();
169+
for _ in 0..num_sources {
170+
if payload.len() < cursor + 34 {
171+
return Err(PythReceiverError::InvalidGovernanceMessage);
172+
}
173+
let emitter_chain_id = u16::from_be_bytes([payload[cursor], payload[cursor + 1]]);
174+
cursor += 2;
175+
176+
let mut emitter_address = [0u8; 32];
177+
emitter_address.copy_from_slice(&payload[cursor..cursor + 32]);
178+
cursor += 32;
179+
180+
sources.push(DataSource {
181+
chain_id: U16::from(emitter_chain_id),
182+
emitter_address: FixedBytes::from(emitter_address),
183+
});
184+
}
185+
GovernancePayload::SetDataSources(SetDataSources { sources })
186+
}
187+
GovernanceAction::SetFee => {
188+
if payload.len() < cursor + 16 {
189+
return Err(PythReceiverError::InvalidGovernanceMessage);
190+
}
191+
let value = u64::from_be_bytes([
192+
payload[cursor],
193+
payload[cursor + 1],
194+
payload[cursor + 2],
195+
payload[cursor + 3],
196+
payload[cursor + 4],
197+
payload[cursor + 5],
198+
payload[cursor + 6],
199+
payload[cursor + 7],
200+
]);
201+
cursor += 8;
202+
let expo = u64::from_be_bytes([
203+
payload[cursor],
204+
payload[cursor + 1],
205+
payload[cursor + 2],
206+
payload[cursor + 3],
207+
payload[cursor + 4],
208+
payload[cursor + 5],
209+
payload[cursor + 6],
210+
payload[cursor + 7],
211+
]);
212+
cursor += 8;
213+
GovernancePayload::SetFee(SetFee { value, expo })
214+
}
215+
GovernanceAction::SetFeeInToken => {
216+
if payload.len() < cursor + 17 {
217+
return Err(PythReceiverError::InvalidGovernanceMessage);
218+
}
219+
let value = u64::from_be_bytes([
220+
payload[cursor],
221+
payload[cursor + 1],
222+
payload[cursor + 2],
223+
payload[cursor + 3],
224+
payload[cursor + 4],
225+
payload[cursor + 5],
226+
payload[cursor + 6],
227+
payload[cursor + 7],
228+
]);
229+
cursor += 8;
230+
let expo = u64::from_be_bytes([
231+
payload[cursor],
232+
payload[cursor + 1],
233+
payload[cursor + 2],
234+
payload[cursor + 3],
235+
payload[cursor + 4],
236+
payload[cursor + 5],
237+
payload[cursor + 6],
238+
payload[cursor + 7],
239+
]);
240+
cursor += 8;
241+
let token_len = payload[cursor];
242+
cursor += 1;
243+
if token_len != 20 {
244+
return Err(PythReceiverError::InvalidGovernanceMessage);
245+
}
246+
if payload.len() < cursor + 20 {
247+
return Err(PythReceiverError::InvalidGovernanceMessage);
248+
}
249+
let mut token_bytes = [0u8; 20];
250+
token_bytes.copy_from_slice(&payload[cursor..cursor + 20]);
251+
cursor += 20;
252+
GovernancePayload::SetFeeInToken(SetFeeInToken {
253+
value,
254+
expo,
255+
token: Address::from(token_bytes),
256+
})
257+
}
258+
GovernanceAction::SetValidPeriod => {
259+
return Err(PythReceiverError::InvalidGovernanceMessage);
260+
}
261+
GovernanceAction::SetWormholeAddress => {
262+
if payload.len() < cursor + 20 {
263+
return Err(PythReceiverError::InvalidGovernanceMessage);
264+
}
265+
let mut address_bytes = [0u8; 20];
266+
address_bytes.copy_from_slice(&payload[cursor..cursor + 20]);
267+
cursor += 20;
268+
GovernancePayload::SetWormholeAddress(SetWormholeAddress {
269+
address: Address::from(address_bytes),
270+
})
271+
}
272+
};
273+
274+
if cursor != payload.len() {
275+
return Err(PythReceiverError::InvalidGovernanceMessage);
276+
}
277+
278+
Ok(GovernanceInstruction {
279+
target_chain_id,
280+
payload: governance_payload,
281+
})
282+
}

target_chains/stylus/contracts/pyth-receiver/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
extern crate alloc;
77

88
mod error;
9+
mod governance_structs;
910
#[cfg(test)]
1011
mod integration_tests;
1112
mod structs;

0 commit comments

Comments
 (0)