Skip to content

Commit 6e62328

Browse files
committed
test(target_chains/starknet): add wormhole contract tests
1 parent 2d9c6d3 commit 6e62328

File tree

5 files changed

+316
-3
lines changed

5 files changed

+316
-3
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
target
2+
.snfoundry_cache/

target_chains/starknet/contracts/Scarb.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ version = 1
44
[[package]]
55
name = "pyth"
66
version = "0.1.0"
7+
dependencies = [
8+
"snforge_std",
9+
]
10+
11+
[[package]]
12+
name = "snforge_std"
13+
version = "0.21.0"
14+
source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.21.0#2996b8c1dd66b2715fc67e69578089f278a46790"

target_chains/starknet/contracts/Scarb.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ edition = "2023_11"
55

66
[dependencies]
77
starknet = ">=2.5.4"
8+
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.21.0" }
89

910
[[target.starknet-contract]]
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
mod pyth;
2-
mod wormhole;
3-
mod reader;
1+
pub mod pyth;
2+
pub mod wormhole;
3+
pub mod reader;
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget};
2+
use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait};
3+
use pyth::reader::{ByteArray, ByteArrayImpl, ReaderImpl};
4+
use core::starknet::ContractAddress;
5+
use core::panic_with_felt252;
6+
7+
#[test]
8+
fn test_parse_and_verify_vm_works() {
9+
let owner = 'owner'.try_into().unwrap();
10+
let dispatcher = deploy_and_init(owner);
11+
12+
let vm = dispatcher.parse_and_verify_vm(good_vm1()).unwrap();
13+
assert!(vm.version == 1);
14+
assert!(vm.guardian_set_index == 3);
15+
assert!(vm.signatures.len() == 13);
16+
assert!(*vm.signatures.at(0).guardian_index == 1);
17+
assert!(*vm.signatures.at(1).guardian_index == 2);
18+
assert!(*vm.signatures.at(12).guardian_index == 18);
19+
assert!(vm.timestamp == 1712589207);
20+
assert!(vm.nonce == 0);
21+
assert!(vm.emitter_chain_id == 26);
22+
assert!(
23+
vm.emitter_address == 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71
24+
);
25+
assert!(vm.sequence == 0x2f03161);
26+
assert!(vm.consistency_level == 1);
27+
assert!(vm.payload.len() == 37);
28+
29+
let mut reader = ReaderImpl::new(vm.payload);
30+
assert!(reader.read_u8().unwrap() == 65);
31+
assert!(reader.read_u8().unwrap() == 85);
32+
assert!(reader.read_u8().unwrap() == 87);
33+
}
34+
35+
#[test]
36+
#[fuzzer(runs: 100, seed: 0)]
37+
#[should_panic(expected: ('any_expected',))]
38+
fn test_parse_and_verify_vm_rejects_corrupted_vm(pos: usize, random1: usize, random2: usize) {
39+
let owner = 'owner'.try_into().unwrap();
40+
let dispatcher = deploy_and_init(owner);
41+
42+
let r = dispatcher.parse_and_verify_vm(corrupted_vm(pos, random1, random2));
43+
match r {
44+
Result::Ok(v) => { println!("no error, output: {:?}", v); },
45+
Result::Err(err) => {
46+
if err == 'invalid signature'
47+
|| err == 'invalid guardian index'
48+
|| err == 'invalid guardian set index'
49+
|| err == 'unexpected end of input'
50+
|| err == 'VM version incompatible' {
51+
panic_with_felt252('any_expected');
52+
} else {
53+
panic_with_felt252(err);
54+
}
55+
},
56+
}
57+
}
58+
59+
#[test]
60+
#[should_panic(expected: ('access denied',))]
61+
fn test_submit_guardian_set_rejects_wrong_owner() {
62+
let owner = 'owner'.try_into().unwrap();
63+
let dispatcher = deploy(owner, guardian_set1());
64+
start_prank(CheatTarget::One(dispatcher.contract_address), 'baddy'.try_into().unwrap());
65+
dispatcher.submit_new_guardian_set(1, guardian_set1());
66+
}
67+
68+
#[test]
69+
#[should_panic(expected: ('invalid guardian set sequence',))]
70+
fn test_submit_guardian_set_rejects_wrong_index() {
71+
let owner = 'owner'.try_into().unwrap();
72+
let dispatcher = deploy(owner, guardian_set1());
73+
74+
start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
75+
dispatcher.submit_new_guardian_set(1, guardian_set1());
76+
dispatcher.submit_new_guardian_set(3, guardian_set3());
77+
}
78+
79+
#[test]
80+
#[should_panic(expected: ('no guardians specified',))]
81+
fn test_deploy_rejects_empty() {
82+
let owner = 'owner'.try_into().unwrap();
83+
deploy(owner, array![]);
84+
}
85+
86+
#[test]
87+
#[should_panic(expected: ('no guardians specified',))]
88+
fn test_submit_guardian_set_rejects_empty() {
89+
let owner = 'owner'.try_into().unwrap();
90+
let dispatcher = deploy(owner, guardian_set1());
91+
92+
start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
93+
dispatcher.submit_new_guardian_set(1, array![]);
94+
}
95+
96+
fn array_left252_to_bytes31(mut input: Array<felt252>) -> Array<bytes31> {
97+
let mut output = array![];
98+
loop {
99+
match input.pop_front() {
100+
Option::Some(v) => { output.append(v.try_into().unwrap()); },
101+
Option::None => { break; },
102+
}
103+
};
104+
output
105+
}
106+
107+
fn deploy(owner: ContractAddress, guardians: Array<felt252>) -> IWormholeDispatcher {
108+
let mut args = array![];
109+
(owner, guardians).serialize(ref args);
110+
let contract = declare("wormhole");
111+
let contract_address = match contract.deploy(@args) {
112+
Result::Ok(v) => { v },
113+
Result::Err(err) => {
114+
panic(err.panic_data);
115+
0.try_into().unwrap()
116+
},
117+
};
118+
IWormholeDispatcher { contract_address }
119+
}
120+
121+
pub fn deploy_and_init(owner: ContractAddress) -> IWormholeDispatcher {
122+
let dispatcher = deploy(owner, guardian_set1());
123+
124+
start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
125+
dispatcher.submit_new_guardian_set(1, guardian_set1());
126+
dispatcher.submit_new_guardian_set(2, guardian_set2());
127+
dispatcher.submit_new_guardian_set(3, guardian_set3());
128+
stop_prank(CheatTarget::One(dispatcher.contract_address));
129+
130+
dispatcher
131+
}
132+
133+
fn corrupted_vm(pos: usize, random1: usize, random2: usize) -> ByteArray {
134+
let mut new_data = array![];
135+
136+
let mut real_data = good_vm1();
137+
// Make sure we select a position not on the last item because
138+
// we didn't implement corrupting an incomplete bytes31.
139+
let pos = pos % (real_data.len() - 31);
140+
let bad_index = pos / 31;
141+
142+
let mut num_last_bytes = 0;
143+
let mut i = 0;
144+
loop {
145+
let (real_bytes, num_bytes) = match real_data.pop_front() {
146+
Option::Some(v) => v,
147+
Option::None => { break; }
148+
};
149+
if num_bytes < 31 {
150+
new_data.append(real_bytes);
151+
num_last_bytes = num_bytes;
152+
break;
153+
}
154+
155+
if i == bad_index {
156+
new_data.append(corrupted_bytes(real_bytes, pos % 31, random1, random2));
157+
} else {
158+
new_data.append(real_bytes);
159+
}
160+
i += 1;
161+
};
162+
ByteArrayImpl::new(new_data, num_last_bytes)
163+
}
164+
165+
// Returns a `bytes31` value with 2 bytes changed. We need to change at least 2 bytes
166+
// because a single byte can be a recovery id, where only 1 bit matters so
167+
// a modification of recovery id can result in a valid VM.
168+
fn corrupted_bytes(input: bytes31, index: usize, random1: usize, random2: usize) -> bytes31 {
169+
let index2 = (index + 1) % 31;
170+
171+
let mut value: u256 = 0;
172+
let mut i = 0;
173+
while i < 31 {
174+
let real_byte = input.at(30 - i);
175+
let new_byte = if i == index {
176+
corrupted_byte(real_byte, random1)
177+
} else if i == index2 {
178+
corrupted_byte(real_byte, random2)
179+
} else {
180+
real_byte
181+
};
182+
value = value * 256 + new_byte.into();
183+
i += 1;
184+
};
185+
let value: felt252 = value.try_into().expect('corrupted bytes overflow');
186+
value.try_into().expect('corrupted bytes overflow')
187+
}
188+
189+
// Returns a byte that's not equal to `value`.
190+
fn corrupted_byte(value: u8, random: usize) -> u8 {
191+
let v: u16 = value.into() + 1 + (random % 255).try_into().unwrap();
192+
(v % 256).try_into().unwrap()
193+
}
194+
195+
// Below are actual guardian keys from
196+
// https://github.com/wormhole-foundation/wormhole-networks/tree/master/mainnetv2/guardianset
197+
fn guardian_set1() -> Array<felt252> {
198+
array![
199+
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,
200+
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157,
201+
0x114De8460193bdf3A2fCf81f86a09765F4762fD1,
202+
0x107A0086b32d7A0977926A205131d8731D39cbEB,
203+
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2,
204+
0x11b39756C042441BE6D8650b69b54EbE715E2343,
205+
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd,
206+
0xeB5F7389Fa26941519f0863349C223b73a6DDEE7,
207+
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0,
208+
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e,
209+
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14,
210+
0xf93124b7c738843CBB89E864c862c38cddCccF95,
211+
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890,
212+
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811,
213+
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3,
214+
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf,
215+
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8,
216+
0x5E1487F35515d02A92753504a8D75471b9f49EdB,
217+
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d,
218+
]
219+
}
220+
fn guardian_set2() -> Array<felt252> {
221+
array![
222+
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,
223+
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157,
224+
0x114De8460193bdf3A2fCf81f86a09765F4762fD1,
225+
0x107A0086b32d7A0977926A205131d8731D39cbEB,
226+
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2,
227+
0x11b39756C042441BE6D8650b69b54EbE715E2343,
228+
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd,
229+
0x66B9590e1c41e0B226937bf9217D1d67Fd4E91F5,
230+
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0,
231+
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e,
232+
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14,
233+
0xf93124b7c738843CBB89E864c862c38cddCccF95,
234+
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890,
235+
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811,
236+
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3,
237+
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf,
238+
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8,
239+
0x5E1487F35515d02A92753504a8D75471b9f49EdB,
240+
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d,
241+
]
242+
}
243+
fn guardian_set3() -> Array<felt252> {
244+
array![
245+
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,
246+
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157,
247+
0x114De8460193bdf3A2fCf81f86a09765F4762fD1,
248+
0x107A0086b32d7A0977926A205131d8731D39cbEB,
249+
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2,
250+
0x11b39756C042441BE6D8650b69b54EbE715E2343,
251+
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd,
252+
0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20,
253+
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0,
254+
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e,
255+
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14,
256+
0xf93124b7c738843CBB89E864c862c38cddCccF95,
257+
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890,
258+
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811,
259+
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3,
260+
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf,
261+
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8,
262+
0x5E1487F35515d02A92753504a8D75471b9f49EdB,
263+
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d,
264+
]
265+
}
266+
267+
// A random VAA pulled from Hermes.
268+
fn good_vm1() -> ByteArray {
269+
let bytes = array![
270+
1766847066033410293701000231337210964058791470455465385734308943533652138,
271+
250126301534699068413432844632573953364878937343368310395142095034982913232,
272+
374780571002258088211231890250917843593951765403462661483498298003400611238,
273+
23190137343211334092589308306056431640588154666326612124726174150537328574,
274+
238750269065878649216923353030193912502813798896051725498208457553032584635,
275+
29844190303057534696518006438077948796328243878877072296680853158289181326,
276+
106329507856770018708432343978518079724691760719405501795955774399597471533,
277+
50779865592261858016477142415230454208001695486195806892438697217059319645,
278+
448669871976126446102256476358498380455807705600424321390063431836375575318,
279+
115682669871397824853706713833773246708114483862317474710603223566297521279,
280+
301634766618012930739391408723909107532790832406455099966028276947414082504,
281+
104473166230846104217366042152018649207811514257244625711402436055500423094,
282+
64445621634231668761998815864645440965239569561546522651415024970517905416,
283+
192317190225976528694195501079591384434869624408066864018183189813956862386,
284+
289982656017597431343118552054719821766658675456661448685110903889153449006,
285+
218840601196095059731241556733624112758673153548932709011933806481899680620,
286+
430933799927481265070475198137531816946660368757134588278434352703899277070,
287+
69322998883710289192076494057541346430050879299268159627180404869988632073,
288+
23862615839737051269352321086490452186237833007444069999578906611768140646,
289+
444634264607471510688862284107804392707600799506487897206707262445172121289,
290+
438038196736233160320436150616293672539386464061037100698335568417587662951,
291+
4682255185797880874381673193118803274635247527626050223938224759013169366,
292+
337620725992972686809095065321563509600769533202700218393281926304544120094,
293+
106657917096532484607371891267699639824731774168349872862335217581425289654,
294+
71240348385993236445536577509595968468284689483611375124653855125285401592,
295+
347603391821038175842934311068097986460257977131947418186118379296987051086,
296+
414263571545410645948841360836383289766662078574048514890988877286444618669,
297+
250301638008739107522011802538487063969565433276260914336890309092111026583,
298+
43192785595291340058788190601908070333310658291317702311902081,
299+
52685537088250779930155363779405986390839624071318818148325576008719597568,
300+
14615204155786886573933667335033405822686404253588533,
301+
];
302+
ByteArrayImpl::new(array_left252_to_bytes31(bytes), 22)
303+
}

0 commit comments

Comments
 (0)