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