Skip to content

Commit 2f18d96

Browse files
authored
doc(target_chains/starknet): add wormhole code docs and small API improvements (#1647)
1 parent c264c31 commit 2f18d96

File tree

6 files changed

+112
-14
lines changed

6 files changed

+112
-14
lines changed

target_chains/starknet/contracts/src/wormhole.cairo

+35-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub use interface::{
1212
};
1313
pub use wormhole::{Event, GuardianSetAdded};
1414

15+
/// Implementation of the Wormhole contract.
1516
#[starknet::contract]
1617
mod wormhole {
1718
use pyth::util::UnwrapWithFelt252;
@@ -30,36 +31,56 @@ mod wormhole {
3031
use core::panic_with_felt252;
3132
use pyth::util::{UNEXPECTED_OVERFLOW};
3233

34+
/// Events emitted by the contract.
3335
#[event]
3436
#[derive(Drop, PartialEq, starknet::Event)]
3537
pub enum Event {
3638
GuardianSetAdded: GuardianSetAdded,
3739
}
3840

41+
/// Emitted when a new guardian set is added.
3942
#[derive(Drop, PartialEq, starknet::Event)]
4043
pub struct GuardianSetAdded {
44+
/// Index of the new guardian set.
4145
pub index: u32,
4246
}
4347

48+
/// Guardian set storage.
4449
#[derive(Drop, Debug, Clone, Serde, starknet::Store)]
4550
struct GuardianSet {
51+
/// Number of guardians in this guardian set.
52+
/// Guardian keys are stored separately.
4653
num_guardians: usize,
54+
/// Timestamp of expiry, or 0 if there is no expiration time.
4755
// XXX: storage doesn't work if we use Option here.
4856
expiration_time: u64,
4957
}
5058

5159
#[storage]
5260
struct Storage {
61+
/// ID of the chain the contract is deployed on.
5362
chain_id: u16,
63+
/// ID of the chain containing the Wormhole governance contract.
5464
governance_chain_id: u16,
65+
/// Address of the Wormhole governance contract.
5566
governance_contract: u256,
67+
/// Index of the last added set.
5668
current_guardian_set_index: u32,
69+
/// For every executed governance actions, contains an entry with
70+
/// key = hash of the message and value = true.
5771
consumed_governance_actions: LegacyMap<u256, bool>,
72+
/// All known guardian sets.
5873
guardian_sets: LegacyMap<u32, GuardianSet>,
59-
// (guardian_set_index, guardian_index) => guardian_address
74+
/// Public keys of guardians in all known guardian sets.
75+
/// Key = (guardian_set_index, guardian_index).
6076
guardian_keys: LegacyMap<(u32, u8), EthAddress>,
6177
}
6278

79+
/// Initializes the contract.
80+
/// `initial_guardians` is the list of public keys of guardians.
81+
/// `chain_id` is the ID of the chain the contract is being deployed on.
82+
/// `governance_chain_id` is the ID of the chain containing the Wormhole governance contract.
83+
/// `governance_contract` is the address of the Wormhole governance contract.
6384
#[constructor]
6485
fn constructor(
6586
ref self: ContractState,
@@ -136,7 +157,14 @@ mod wormhole {
136157
keys.append(self.guardian_keys.read((index, i)));
137158
i += 1;
138159
};
139-
super::GuardianSet { keys, expiration_time: set.expiration_time }
160+
super::GuardianSet {
161+
keys,
162+
expiration_time: if set.expiration_time == 0 {
163+
Option::None
164+
} else {
165+
Option::Some(set.expiration_time)
166+
}
167+
}
140168
}
141169
fn get_current_guardian_set_index(self: @ContractState) -> u32 {
142170
self.current_guardian_set_index.read()
@@ -182,6 +210,8 @@ mod wormhole {
182210

183211
#[generate_trait]
184212
impl PrivateImpl of PrivateImplTrait {
213+
/// Validates the new guardian set and writes it to the storage.
214+
/// `SubmitNewGuardianSetError` enumerates possible panic payloads.
185215
fn store_guardian_set(
186216
ref self: ContractState, set_index: u32, guardians: @Array<EthAddress>
187217
) {
@@ -214,12 +244,15 @@ mod wormhole {
214244
self.current_guardian_set_index.write(set_index);
215245
}
216246

247+
/// Marks the specified guardian set to expire in 24 hours.
217248
fn expire_guardian_set(ref self: ContractState, set_index: u32, now: u64) {
218249
let mut set = self.guardian_sets.read(set_index);
219250
set.expiration_time = now + 86400;
220251
self.guardian_sets.write(set_index, set);
221252
}
222253

254+
/// Checks required properties of the governance instruction.
255+
/// `GovernanceError` enumerates possible panic payloads.
223256
fn verify_governance_vm(self: @ContractState, vm: @VerifiedVM) {
224257
if self.current_guardian_set_index.read() != *vm.guardian_set_index {
225258
panic_with_felt252(GovernanceError::NotCurrentGuardianSet.into());

target_chains/starknet/contracts/src/wormhole/errors.cairo

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/// Possible panic payloads for methods that execute governance instructions.
12
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
23
pub enum GovernanceError {
34
InvalidModule,
@@ -25,6 +26,7 @@ impl GovernanceErrorIntoFelt252 of Into<GovernanceError, felt252> {
2526
}
2627
}
2728

29+
/// Possible panic payloads of `IWormhole::submit_new_guardian_set`.
2830
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
2931
pub enum SubmitNewGuardianSetError {
3032
Governance: GovernanceError,
@@ -33,7 +35,6 @@ pub enum SubmitNewGuardianSetError {
3335
InvalidGuardianKey,
3436
// guardian set index must increase in steps of 1
3537
InvalidGuardianSetSequence,
36-
AccessDenied,
3738
}
3839

3940
impl SubmitNewGuardianSetErrorIntoFelt252 of Into<SubmitNewGuardianSetError, felt252> {
@@ -44,12 +45,11 @@ impl SubmitNewGuardianSetErrorIntoFelt252 of Into<SubmitNewGuardianSetError, fel
4445
SubmitNewGuardianSetError::TooManyGuardians => 'too many guardians',
4546
SubmitNewGuardianSetError::InvalidGuardianKey => 'invalid guardian key',
4647
SubmitNewGuardianSetError::InvalidGuardianSetSequence => 'invalid guardian set sequence',
47-
SubmitNewGuardianSetError::AccessDenied => 'access denied',
4848
}
4949
}
5050
}
5151

52-
52+
/// Possible panic payloads of `IWormhole::parse_and_verify_vm`.
5353
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
5454
pub enum ParseAndVerifyVmError {
5555
Reader: pyth::reader::Error,
@@ -77,6 +77,7 @@ impl ErrorIntoFelt252 of Into<ParseAndVerifyVmError, felt252> {
7777
}
7878
}
7979

80+
/// Possible panic payloads of `IWormhole::get_guardian_set`.
8081
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
8182
pub enum GetGuardianSetError {
8283
InvalidIndex,

target_chains/starknet/contracts/src/wormhole/governance.cairo

+12
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,29 @@ impl U8TryIntoAction of TryInto<u8, Action> {
3131
}
3232
}
3333

34+
/// Parsed header of a governance message.
3435
#[derive(Drop, Debug, Clone)]
3536
pub struct Header {
37+
/// The destination module of this instruction.
3638
pub module: u256,
39+
/// Type of action.
3740
pub action: Action,
41+
/// The destination chain ID of this instruction,
42+
/// or 0 if it's applicable to all chains.
3843
pub chain_id: u16,
3944
}
4045

46+
/// Payload of `GuardianSetUpgrade` instruction.
4147
#[derive(Drop, Debug, Clone)]
4248
pub struct NewGuardianSet {
49+
/// Index of the new set.
4350
pub set_index: u32,
51+
/// Public keys of guardians, in order.
4452
pub keys: Array<EthAddress>,
4553
}
4654

55+
/// Parses the header of a governance instruction and verifies the module.
56+
/// `GovernanceError` enumerates possible panic payloads.
4757
pub fn parse_header(ref reader: Reader) -> Header {
4858
let module = reader.read_u256();
4959
if module != MODULE {
@@ -55,6 +65,8 @@ pub fn parse_header(ref reader: Reader) -> Header {
5565
Header { module, action, chain_id }
5666
}
5767

68+
/// Parses the payload of `GuardianSetUpgrade` instruction.
69+
/// `GovernanceError` enumerates possible panic payloads.
5870
pub fn parse_new_guardian_set(ref reader: Reader) -> NewGuardianSet {
5971
let set_index = reader.read_u32();
6072
let num_guardians = reader.read_u8();

target_chains/starknet/contracts/src/wormhole/interface.cairo

+49-1
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,84 @@ use pyth::byte_array::ByteArray;
33
use core::starknet::secp256_trait::Signature;
44
use core::starknet::EthAddress;
55

6+
/// Wormhole provides a secure means for communication between multiple chains.
7+
/// This contract allows users to parse and verify a Wormhole message that informs
8+
/// them about a message that was produced by a contract on a Wormhole-supported chain.
9+
///
10+
/// Note that this implementation does not support creating Wormhole messages.
611
#[starknet::interface]
712
pub trait IWormhole<T> {
13+
/// Parses and returns the contents of the message. Panics if there was a
14+
/// parsing error or if signature verification failed.
15+
/// `ParseAndVerifyVmError` enumerates possible panic payloads.
816
fn parse_and_verify_vm(self: @T, encoded_vm: ByteArray) -> VerifiedVM;
917

18+
/// Returns the list of guardians at the specified index.
19+
/// `GetGuardianSetError` enumerates possible panic payloads.
1020
fn get_guardian_set(self: @T, index: u32) -> GuardianSet;
21+
22+
/// Returns the index of the latest guardian set. Guardian sets with
23+
/// lower indexes may still be supported unless they have already expired.
1124
fn get_current_guardian_set_index(self: @T) -> u32;
25+
26+
/// Checks whether the governance action with the specified hash has already
27+
/// been consumed by this contract. Actions that have been consumed cannot
28+
/// be executed again.
1229
fn governance_action_is_consumed(self: @T, hash: u256) -> bool;
30+
31+
/// Returns the ID of the chain on which the contract has been deployed.
1332
fn chain_id(self: @T) -> u16;
33+
34+
/// Returns the ID of the chain containing the Wormhole governance contract.
1435
fn governance_chain_id(self: @T) -> u16;
36+
37+
/// Returns the address of the Wormhole governance contract.
1538
fn governance_contract(self: @T) -> u256;
1639

1740
// We don't need to implement other governance actions for now.
1841
// Instead of upgrading the Wormhole contract, we can switch to another Wormhole address
1942
// in the Pyth contract.
43+
44+
/// Executes a governance instruction to add a new guardian set. The new set becomes
45+
/// active immediately. The previous guardian set will be available for 24 hours and then expire.
46+
/// `SubmitNewGuardianSetError` enumerates possible panic payloads.
2047
fn submit_new_guardian_set(ref self: T, encoded_vm: ByteArray);
2148
}
2249

50+
/// Information about a guardian's signature within a message.
2351
#[derive(Drop, Debug, Clone, Serde)]
2452
pub struct GuardianSignature {
53+
/// Index of this guardian within the guardian set.
2554
pub guardian_index: u8,
55+
/// The guardian's signature of the message.
2656
pub signature: Signature,
2757
}
2858

59+
/// A verified Wormhole message.
2960
#[derive(Drop, Debug, Clone, Serde)]
3061
pub struct VerifiedVM {
62+
/// Version of the encoding format.
3163
pub version: u8,
64+
/// Index of the guardian set that signed this message.
3265
pub guardian_set_index: u32,
66+
/// Signatures of guardians.
3367
pub signatures: Array<GuardianSignature>,
68+
/// Creation time of the message.
3469
pub timestamp: u32,
70+
/// Unique nonce of the message.
3571
pub nonce: u32,
72+
/// ID of the chain on which the message was sent.
3673
pub emitter_chain_id: u16,
74+
/// Address of the contract that sent the message.
3775
pub emitter_address: u256,
76+
/// Sequence number of the message.
3877
pub sequence: u64,
78+
/// Observed consistency level (specific to the sender's chain).
3979
pub consistency_level: u8,
80+
/// The data attached to the message.
4081
pub payload: ByteArray,
82+
/// Double keccak256 hash of all fields of the message, excluding `version`,
83+
/// `guardian_set_index` and `signatures`.
4184
pub hash: u256,
4285
}
4386

@@ -47,8 +90,13 @@ pub fn quorum(num_guardians: usize) -> usize {
4790
((num_guardians * 2) / 3) + 1
4891
}
4992

93+
/// Information about a guardian set.
5094
#[derive(Drop, Debug, Clone, Serde)]
5195
pub struct GuardianSet {
96+
/// Public keys of guardians, in order.
5297
pub keys: Array<EthAddress>,
53-
pub expiration_time: u64,
98+
/// Timestamp of expiration, if any. The contract will not verify messages signed by
99+
/// this guardian set if the guardian set has expired. The latest guardian set
100+
/// does not have an expiration time.
101+
pub expiration_time: Option<u64>,
54102
}

target_chains/starknet/contracts/src/wormhole/parse_vm.cairo

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use core::starknet::secp256_trait::Signature;
55
use pyth::byte_array::ByteArray;
66
use core::panic_with_felt252;
77

8+
/// Parses information about a guardian signature within a Wormhole message.
9+
/// `pyth::reader::Error` enumerates possible panic payloads.
810
fn parse_signature(ref reader: Reader) -> GuardianSignature {
911
let guardian_index = reader.read_u8();
1012
let r = reader.read_u256();
@@ -14,6 +16,8 @@ fn parse_signature(ref reader: Reader) -> GuardianSignature {
1416
GuardianSignature { guardian_index, signature: Signature { r, s, y_parity } }
1517
}
1618

19+
/// Parses a Wormhole message.
20+
/// `ParseAndVerifyVmError` enumerates possible panic payloads.
1721
pub fn parse_vm(encoded_vm: ByteArray) -> VerifiedVM {
1822
let mut reader = ReaderImpl::new(encoded_vm);
1923
let version = reader.read_u8();

target_chains/starknet/contracts/tests/wormhole.cairo

+8-8
Original file line numberDiff line numberDiff line change
@@ -156,38 +156,38 @@ fn test_get_guardian_set_works() {
156156

157157
let set0 = dispatcher.get_guardian_set(0);
158158
assert!(set0.keys == guardian_set0());
159-
assert!(set0.expiration_time == 0);
159+
assert!(set0.expiration_time.is_none());
160160
assert!(dispatcher.get_current_guardian_set_index() == 0);
161161

162162
dispatcher.submit_new_guardian_set(data::mainnet_guardian_set_upgrade1());
163163
let set0 = dispatcher.get_guardian_set(0);
164164
assert!(set0.keys == guardian_set0());
165-
assert!(set0.expiration_time != 0);
165+
assert!(set0.expiration_time.is_some());
166166
let set1 = dispatcher.get_guardian_set(1);
167167
assert!(set1.keys == guardian_set1());
168-
assert!(set1.expiration_time == 0);
168+
assert!(set1.expiration_time.is_none());
169169
assert!(dispatcher.get_current_guardian_set_index() == 1);
170170

171171
dispatcher.submit_new_guardian_set(data::mainnet_guardian_set_upgrade2());
172172
let set0 = dispatcher.get_guardian_set(0);
173173
assert!(set0.keys == guardian_set0());
174-
assert!(set0.expiration_time != 0);
174+
assert!(set0.expiration_time.is_some());
175175
let set1 = dispatcher.get_guardian_set(1);
176176
assert!(set1.keys == guardian_set1());
177-
assert!(set1.expiration_time != 0);
177+
assert!(set1.expiration_time.is_some());
178178
let set2 = dispatcher.get_guardian_set(2);
179179
assert!(set2.keys == guardian_set2());
180-
assert!(set2.expiration_time == 0);
180+
assert!(set2.expiration_time.is_none());
181181
assert!(dispatcher.get_current_guardian_set_index() == 2);
182182

183183
dispatcher.submit_new_guardian_set(data::mainnet_guardian_set_upgrade3());
184184
dispatcher.submit_new_guardian_set(data::mainnet_guardian_set_upgrade4());
185185
let set3 = dispatcher.get_guardian_set(3);
186186
assert!(set3.keys == guardian_set3());
187-
assert!(set3.expiration_time != 0);
187+
assert!(set3.expiration_time.is_some());
188188
let set4 = dispatcher.get_guardian_set(4);
189189
assert!(set4.keys == guardian_set4());
190-
assert!(set4.expiration_time == 0);
190+
assert!(set4.expiration_time.is_none());
191191
assert!(dispatcher.get_current_guardian_set_index() == 4);
192192
}
193193

0 commit comments

Comments
 (0)