PythGovernance.t.sol 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. // SPDX-License-Identifier: Apache 2
  2. // NOTE: These tests were migrated from target_chains/ethereum/contracts/test/pyth.js but exclude the Wormhole-specific tests,
  3. // which remain in the original JavaScript test file.
  4. pragma solidity ^0.8.0;
  5. import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
  6. import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
  7. import "forge-std/Test.sol";
  8. import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
  9. import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
  10. import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
  11. import "../contracts/pyth/PythInternalStructs.sol";
  12. import "../contracts/pyth/PythGovernanceInstructions.sol";
  13. import "../contracts/pyth/PythUpgradable.sol";
  14. import "../contracts/pyth/PythGetters.sol";
  15. import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
  16. import "../contracts/wormhole/interfaces/IWormhole.sol";
  17. import "../contracts/wormhole/Implementation.sol";
  18. import "../contracts/wormhole/Setup.sol";
  19. import "../contracts/wormhole/Wormhole.sol";
  20. import "../contracts/wormhole-receiver/WormholeReceiver.sol";
  21. import "../contracts/wormhole-receiver/ReceiverImplementation.sol";
  22. import "../contracts/wormhole-receiver/ReceiverSetup.sol";
  23. import "../contracts/wormhole-receiver/ReceiverGovernanceStructs.sol";
  24. import "../contracts/wormhole-receiver/ReceiverStructs.sol";
  25. import "../contracts/wormhole-receiver/ReceiverGovernance.sol";
  26. import "../contracts/libraries/external/BytesLib.sol";
  27. import "../contracts/pyth/mock/MockUpgradeableProxy.sol";
  28. import "./utils/WormholeTestUtils.t.sol";
  29. import "./utils/PythTestUtils.t.sol";
  30. import "./utils/RandTestUtils.t.sol";
  31. contract PythGovernanceTest is
  32. Test,
  33. WormholeTestUtils,
  34. PythTestUtils,
  35. PythGovernanceInstructions
  36. {
  37. using BytesLib for bytes;
  38. IPyth public pyth;
  39. address constant TEST_SIGNER1 = 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe;
  40. address constant TEST_SIGNER2 = 0x4ba0C2db9A26208b3bB1a50B01b16941c10D76db;
  41. uint16 constant TEST_GOVERNANCE_CHAIN_ID = 1;
  42. bytes32 constant TEST_GOVERNANCE_EMITTER =
  43. 0x0000000000000000000000000000000000000000000000000000000000000011;
  44. uint16 constant TEST_PYTH2_WORMHOLE_CHAIN_ID = 1;
  45. bytes32 constant TEST_PYTH2_WORMHOLE_EMITTER =
  46. 0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b;
  47. uint16 constant TARGET_CHAIN_ID = 2;
  48. function setUp() public {
  49. pyth = IPyth(setUpPyth(setUpWormholeReceiver(1)));
  50. }
  51. function testNoOwner() public {
  52. // Check that the ownership is renounced
  53. assertEq(OwnableUpgradeable(address(pyth)).owner(), address(0));
  54. }
  55. function testValidDataSources() public {
  56. assertTrue(
  57. PythGetters(address(pyth)).isValidDataSource(
  58. TEST_PYTH2_WORMHOLE_CHAIN_ID,
  59. TEST_PYTH2_WORMHOLE_EMITTER
  60. )
  61. );
  62. }
  63. function testSetFee() public {
  64. // Set fee to 5000 (5000 = 5 * 10^3)
  65. bytes memory setFeeMessage = abi.encodePacked(
  66. MAGIC,
  67. uint8(GovernanceModule.Target),
  68. uint8(GovernanceAction.SetFee),
  69. TARGET_CHAIN_ID,
  70. uint64(5), // value
  71. uint64(3) // exponent
  72. );
  73. bytes memory vaa = encodeAndSignMessage(
  74. setFeeMessage,
  75. TEST_GOVERNANCE_CHAIN_ID,
  76. TEST_GOVERNANCE_EMITTER,
  77. 1
  78. );
  79. uint oldFee = PythGetters(address(pyth)).singleUpdateFeeInWei();
  80. vm.expectEmit(true, true, true, true);
  81. emit FeeSet(oldFee, 5000);
  82. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  83. assertEq(PythGetters(address(pyth)).singleUpdateFeeInWei(), 5000);
  84. }
  85. function testSetValidPeriod() public {
  86. // Create governance VAA to set valid period to 0
  87. bytes memory data = abi.encodePacked(
  88. MAGIC,
  89. uint8(GovernanceModule.Target),
  90. uint8(GovernanceAction.SetValidPeriod),
  91. TARGET_CHAIN_ID, // Target chain ID
  92. uint64(0) // New valid period
  93. );
  94. bytes memory vaa = encodeAndSignMessage(
  95. data,
  96. TEST_GOVERNANCE_CHAIN_ID,
  97. TEST_GOVERNANCE_EMITTER,
  98. 1
  99. );
  100. uint oldValidPeriod = PythGetters(address(pyth))
  101. .validTimePeriodSeconds();
  102. vm.expectEmit(true, true, true, true);
  103. emit ValidPeriodSet(oldValidPeriod, 0);
  104. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  105. assertEq(PythGetters(address(pyth)).validTimePeriodSeconds(), 0);
  106. }
  107. function testInvalidGovernanceMessage() public {
  108. // Test with wrong magic number
  109. bytes memory data = abi.encodePacked(
  110. bytes4(0x12345678), // Wrong magic
  111. uint8(GovernanceModule.Target),
  112. uint8(GovernanceAction.SetValidPeriod),
  113. uint16(1), // Target chain ID
  114. uint64(0)
  115. );
  116. bytes memory vaa = encodeAndSignMessage(
  117. data,
  118. TEST_GOVERNANCE_CHAIN_ID,
  119. TEST_GOVERNANCE_EMITTER,
  120. 1
  121. );
  122. vm.expectRevert(PythErrors.InvalidGovernanceMessage.selector);
  123. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  124. }
  125. function testInvalidGovernanceTarget() public {
  126. // Test with wrong chain target
  127. bytes memory data = abi.encodePacked(
  128. MAGIC,
  129. uint8(GovernanceModule.Target),
  130. uint8(GovernanceAction.SetValidPeriod),
  131. uint16(3), // Different chain ID for testing invalid target
  132. uint64(0)
  133. );
  134. bytes memory vaa = encodeAndSignMessage(
  135. data,
  136. TEST_GOVERNANCE_CHAIN_ID,
  137. TEST_GOVERNANCE_EMITTER,
  138. 1
  139. );
  140. vm.expectRevert(PythErrors.InvalidGovernanceTarget.selector);
  141. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  142. }
  143. function testInvalidGovernanceDataSource() public {
  144. // Test with wrong emitter
  145. bytes memory data = abi.encodePacked(
  146. MAGIC,
  147. uint8(GovernanceModule.Target),
  148. uint8(GovernanceAction.SetValidPeriod),
  149. TARGET_CHAIN_ID,
  150. uint64(0)
  151. );
  152. bytes memory vaa = encodeAndSignMessage(
  153. data,
  154. TEST_GOVERNANCE_CHAIN_ID,
  155. bytes32(uint256(0x1111)), // Wrong emitter
  156. 1
  157. );
  158. vm.expectRevert(PythErrors.InvalidGovernanceDataSource.selector);
  159. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  160. }
  161. function testSetDataSources() public {
  162. // Create governance VAA to set new data sources
  163. bytes memory data = abi.encodePacked(
  164. MAGIC,
  165. uint8(GovernanceModule.Target),
  166. uint8(GovernanceAction.SetDataSources),
  167. TARGET_CHAIN_ID, // Target chain ID
  168. uint8(1), // Number of data sources
  169. uint16(1), // Chain ID
  170. bytes32(
  171. 0x0000000000000000000000000000000000000000000000000000000000001111
  172. ) // Emitter
  173. );
  174. bytes memory vaa = encodeAndSignMessage(
  175. data,
  176. TEST_GOVERNANCE_CHAIN_ID,
  177. TEST_GOVERNANCE_EMITTER,
  178. 1
  179. );
  180. PythInternalStructs.DataSource[] memory oldDataSources = PythGetters(
  181. address(pyth)
  182. ).validDataSources();
  183. PythInternalStructs.DataSource[]
  184. memory newDataSources = new PythInternalStructs.DataSource[](1);
  185. newDataSources[0] = PythInternalStructs.DataSource(
  186. 1,
  187. 0x0000000000000000000000000000000000000000000000000000000000001111
  188. );
  189. vm.expectEmit(true, true, true, true);
  190. emit DataSourcesSet(oldDataSources, newDataSources);
  191. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  192. // Verify old data source is no longer valid
  193. assertFalse(
  194. PythGetters(address(pyth)).isValidDataSource(
  195. TEST_PYTH2_WORMHOLE_CHAIN_ID,
  196. TEST_PYTH2_WORMHOLE_EMITTER
  197. )
  198. );
  199. // Verify new data source is valid
  200. assertTrue(
  201. PythGetters(address(pyth)).isValidDataSource(
  202. 1,
  203. 0x0000000000000000000000000000000000000000000000000000000000001111
  204. )
  205. );
  206. }
  207. function testSetWormholeAddress() public {
  208. // Deploy a new wormhole contract
  209. address newWormhole = address(setUpWormholeReceiver(1));
  210. // Create governance VAA to set new wormhole address
  211. bytes memory data = abi.encodePacked(
  212. MAGIC,
  213. uint8(GovernanceModule.Target),
  214. uint8(GovernanceAction.SetWormholeAddress),
  215. TARGET_CHAIN_ID, // Target chain ID
  216. newWormhole // New wormhole address
  217. );
  218. bytes memory vaa = encodeAndSignMessage(
  219. data,
  220. TEST_GOVERNANCE_CHAIN_ID,
  221. TEST_GOVERNANCE_EMITTER,
  222. 1
  223. );
  224. address oldWormhole = address(PythGetters(address(pyth)).wormhole());
  225. vm.expectEmit(true, true, true, true);
  226. emit WormholeAddressSet(oldWormhole, newWormhole);
  227. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  228. assertEq(address(PythGetters(address(pyth)).wormhole()), newWormhole);
  229. }
  230. function testTransferGovernanceDataSource() public {
  231. uint16 newEmitterChain = 2;
  232. bytes32 newEmitterAddress = 0x0000000000000000000000000000000000000000000000000000000000001111;
  233. // Create claim VAA from new governance
  234. bytes memory claimData = abi.encodePacked(
  235. MAGIC,
  236. uint8(GovernanceModule.Target),
  237. uint8(GovernanceAction.RequestGovernanceDataSourceTransfer),
  238. TARGET_CHAIN_ID, // Target chain ID
  239. uint32(1) // New governance index
  240. );
  241. bytes memory claimVaa = encodeAndSignMessage(
  242. claimData,
  243. newEmitterChain,
  244. newEmitterAddress,
  245. 1
  246. );
  247. // Create authorize VAA from current governance
  248. bytes memory authData = abi.encodePacked(
  249. MAGIC,
  250. uint8(GovernanceModule.Target),
  251. uint8(GovernanceAction.AuthorizeGovernanceDataSourceTransfer),
  252. TARGET_CHAIN_ID, // Target chain ID
  253. claimVaa
  254. );
  255. bytes memory authVaa = encodeAndSignMessage(
  256. authData,
  257. TEST_GOVERNANCE_CHAIN_ID,
  258. TEST_GOVERNANCE_EMITTER,
  259. 1
  260. );
  261. PythInternalStructs.DataSource
  262. memory oldDataSource = PythInternalStructs.DataSource(
  263. TEST_GOVERNANCE_CHAIN_ID,
  264. TEST_GOVERNANCE_EMITTER
  265. );
  266. PythInternalStructs.DataSource
  267. memory newDataSource = PythInternalStructs.DataSource(
  268. newEmitterChain,
  269. newEmitterAddress
  270. );
  271. vm.expectEmit(true, true, true, true);
  272. emit GovernanceDataSourceSet(oldDataSource, newDataSource, 1);
  273. PythGovernance(address(pyth)).executeGovernanceInstruction(authVaa);
  274. // Verify old governance can't execute instructions anymore
  275. bytes memory invalidData = abi.encodePacked(
  276. MAGIC,
  277. uint8(GovernanceModule.Target),
  278. uint8(GovernanceAction.SetValidPeriod),
  279. uint16(1), // Wrong chain ID for testing invalid target
  280. uint64(0)
  281. );
  282. bytes memory invalidVaa = encodeAndSignMessage(
  283. invalidData,
  284. TEST_GOVERNANCE_CHAIN_ID,
  285. TEST_GOVERNANCE_EMITTER,
  286. 2
  287. );
  288. vm.expectRevert(PythErrors.InvalidGovernanceDataSource.selector);
  289. PythGovernance(address(pyth)).executeGovernanceInstruction(invalidVaa);
  290. }
  291. function testSequentialGovernanceMessages() public {
  292. // First governance message
  293. bytes memory data1 = abi.encodePacked(
  294. MAGIC,
  295. uint8(GovernanceModule.Target),
  296. uint8(GovernanceAction.SetValidPeriod),
  297. TARGET_CHAIN_ID, // Target chain ID
  298. uint64(10)
  299. );
  300. bytes memory vaa1 = encodeAndSignMessage(
  301. data1,
  302. TEST_GOVERNANCE_CHAIN_ID,
  303. TEST_GOVERNANCE_EMITTER,
  304. 1
  305. );
  306. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa1);
  307. // Second governance message
  308. bytes memory data2 = abi.encodePacked(
  309. MAGIC,
  310. uint8(GovernanceModule.Target),
  311. uint8(GovernanceAction.SetValidPeriod),
  312. TARGET_CHAIN_ID, // Target chain ID
  313. uint64(20)
  314. );
  315. bytes memory vaa2 = encodeAndSignMessage(
  316. data2,
  317. TEST_GOVERNANCE_CHAIN_ID,
  318. TEST_GOVERNANCE_EMITTER,
  319. 2
  320. );
  321. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa2);
  322. // Try to replay first message
  323. vm.expectRevert(PythErrors.OldGovernanceMessage.selector);
  324. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa1);
  325. // Try to replay second message
  326. vm.expectRevert(PythErrors.OldGovernanceMessage.selector);
  327. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa2);
  328. }
  329. function testUpgradeContractWithChainIdZeroIsInvalid() public {
  330. // Deploy a new PythUpgradable contract
  331. PythUpgradable newImplementation = new PythUpgradable();
  332. // Create governance VAA with chain ID 0 (unset)
  333. bytes memory data = abi.encodePacked(
  334. MAGIC,
  335. uint8(GovernanceModule.Target),
  336. uint8(GovernanceAction.UpgradeContract),
  337. uint16(0), // Chain ID 0 (unset)
  338. address(newImplementation) // New implementation address
  339. );
  340. bytes memory vaa = encodeAndSignMessage(
  341. data,
  342. TEST_GOVERNANCE_CHAIN_ID,
  343. TEST_GOVERNANCE_EMITTER,
  344. 1
  345. );
  346. // Should revert with InvalidGovernanceTarget
  347. vm.expectRevert(PythErrors.InvalidGovernanceTarget.selector);
  348. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  349. }
  350. // Helper function to get the second address from event data
  351. function getSecondAddressFromEventData(
  352. bytes memory data
  353. ) internal pure returns (address) {
  354. (, address secondAddr) = abi.decode(data, (address, address));
  355. return secondAddr;
  356. }
  357. function testUpgradeContractShouldWork() public {
  358. // Deploy a new PythUpgradable contract
  359. PythUpgradable newImplementation = new PythUpgradable();
  360. // Create governance VAA to upgrade the contract
  361. bytes memory data = abi.encodePacked(
  362. MAGIC,
  363. uint8(GovernanceModule.Target),
  364. uint8(GovernanceAction.UpgradeContract),
  365. TARGET_CHAIN_ID, // Valid target chain ID
  366. address(newImplementation) // New implementation address
  367. );
  368. bytes memory vaa = encodeAndSignMessage(
  369. data,
  370. TEST_GOVERNANCE_CHAIN_ID,
  371. TEST_GOVERNANCE_EMITTER,
  372. 1
  373. );
  374. // Create a custom event checker for ContractUpgraded event
  375. // Since we only care about the newImplementation parameter
  376. vm.recordLogs();
  377. // Execute the governance instruction
  378. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  379. // Get emitted logs and check the event parameters
  380. Vm.Log[] memory entries = vm.getRecordedLogs();
  381. bool foundUpgradeEvent = false;
  382. for (uint i = 0; i < entries.length; i++) {
  383. // The event signature for ContractUpgraded
  384. bytes32 eventSignature = keccak256(
  385. "ContractUpgraded(address,address)"
  386. );
  387. if (entries[i].topics[0] == eventSignature) {
  388. // This is a ContractUpgraded event
  389. // Get just the new implementation address using our helper
  390. address recordedNewImplementation = getSecondAddressFromEventData(
  391. entries[i].data
  392. );
  393. // Check newImplementation
  394. assertEq(recordedNewImplementation, address(newImplementation));
  395. foundUpgradeEvent = true;
  396. break;
  397. }
  398. }
  399. // Make sure we found the event
  400. assertTrue(foundUpgradeEvent, "ContractUpgraded event not found");
  401. // Verify the upgrade worked by checking the magic number
  402. assertEq(
  403. PythUpgradable(address(pyth)).pythUpgradableMagic(),
  404. 0x97a6f304
  405. );
  406. // Verify the implementation was upgraded to our new implementation
  407. // Access implementation using the ERC1967 storage slot
  408. address implAddr = address(
  409. uint160(
  410. uint256(
  411. vm.load(
  412. address(pyth),
  413. 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc // ERC1967 implementation slot
  414. )
  415. )
  416. )
  417. );
  418. assertEq(implAddr, address(newImplementation));
  419. }
  420. function testUpgradeContractToNonPythContractWontWork() public {
  421. // Deploy a mock upgradeable proxy that isn't a proper Pyth implementation
  422. MockUpgradeableProxy newImplementation = new MockUpgradeableProxy();
  423. // Create governance VAA to upgrade to an invalid implementation
  424. bytes memory data = abi.encodePacked(
  425. MAGIC,
  426. uint8(GovernanceModule.Target),
  427. uint8(GovernanceAction.UpgradeContract),
  428. TARGET_CHAIN_ID, // Valid target chain ID
  429. address(newImplementation) // Invalid implementation address
  430. );
  431. bytes memory vaa = encodeAndSignMessage(
  432. data,
  433. TEST_GOVERNANCE_CHAIN_ID,
  434. TEST_GOVERNANCE_EMITTER,
  435. 1
  436. );
  437. // Should revert with no specific error message because the mock implementation
  438. // doesn't have the pythUpgradableMagic method
  439. vm.expectRevert();
  440. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  441. }
  442. function testSetTransactionFee() public {
  443. // Set transaction fee to 1000 (1000 = 1 * 10^3)
  444. bytes memory setTransactionFeeMessage = abi.encodePacked(
  445. MAGIC,
  446. uint8(GovernanceModule.Target),
  447. uint8(GovernanceAction.SetTransactionFee),
  448. TARGET_CHAIN_ID,
  449. uint64(1), // value
  450. uint64(3) // exponent
  451. );
  452. bytes memory vaa = encodeAndSignMessage(
  453. setTransactionFeeMessage,
  454. TEST_GOVERNANCE_CHAIN_ID,
  455. TEST_GOVERNANCE_EMITTER,
  456. 1
  457. );
  458. uint oldFee = PythGetters(address(pyth)).transactionFeeInWei();
  459. vm.expectEmit(true, true, true, true);
  460. emit TransactionFeeSet(oldFee, 1000);
  461. PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
  462. assertEq(PythGetters(address(pyth)).transactionFeeInWei(), 1000);
  463. // Test that update fee includes transaction fee
  464. bytes[] memory updateData = new bytes[](0);
  465. assertEq(pyth.getUpdateFee(updateData), 1000);
  466. // Test that insufficient fee reverts
  467. vm.expectRevert(PythErrors.InsufficientFee.selector);
  468. pyth.updatePriceFeeds{value: 999}(updateData);
  469. // Test that sufficient fee works
  470. pyth.updatePriceFeeds{value: 1000}(updateData);
  471. }
  472. function encodeAndSignWormholeMessage(
  473. bytes memory data,
  474. uint16 emitterChainId,
  475. bytes32 emitterAddress,
  476. uint64 sequence,
  477. uint8 numGuardians
  478. ) internal view returns (bytes memory) {
  479. return
  480. generateVaa(
  481. uint32(block.timestamp),
  482. emitterChainId,
  483. emitterAddress,
  484. sequence,
  485. data,
  486. numGuardians
  487. );
  488. }
  489. function encodeAndSignMessage(
  490. bytes memory data,
  491. uint16 emitterChainId,
  492. bytes32 emitterAddress,
  493. uint64 sequence
  494. ) internal view returns (bytes memory) {
  495. return
  496. encodeAndSignWormholeMessage(
  497. data,
  498. emitterChainId,
  499. emitterAddress,
  500. sequence,
  501. 1 // Number of guardians
  502. );
  503. }
  504. // Events
  505. event ContractUpgraded(
  506. address oldImplementation,
  507. address newImplementation
  508. );
  509. event GovernanceDataSourceSet(
  510. PythInternalStructs.DataSource oldDataSource,
  511. PythInternalStructs.DataSource newDataSource,
  512. uint64 initialSequence
  513. );
  514. event DataSourcesSet(
  515. PythInternalStructs.DataSource[] oldDataSources,
  516. PythInternalStructs.DataSource[] newDataSources
  517. );
  518. event FeeSet(uint oldFee, uint newFee);
  519. event ValidPeriodSet(uint oldValidPeriod, uint newValidPeriod);
  520. event WormholeAddressSet(
  521. address oldWormholeAddress,
  522. address newWormholeAddress
  523. );
  524. event TransactionFeeSet(uint oldFee, uint newFee);
  525. }