diff --git a/target_chains/stylus/contracts/pyth-receiver/src/error.rs b/target_chains/stylus/contracts/pyth-receiver/src/error.rs index caeb2e4922..b195711098 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/error.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/error.rs @@ -20,11 +20,105 @@ pub enum PythReceiverError { PriceFeedNotFoundWithinRange, NoFreshUpdate, PriceFeedNotFound, + InvalidGovernanceMessage, + InvalidGovernanceTarget, + InvalidGovernanceAction, + InvalidGovernanceDataSource, + OldGovernanceMessage, + GovernanceMessageAlreadyExecuted, + InvalidWormholeAddressToSet, + WormholeUninitialized, } impl core::fmt::Debug for PythReceiverError { - fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - Ok(()) + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + PythReceiverError::PriceUnavailable => write!(f, "PriceUnavailable"), + PythReceiverError::InvalidUpdateData => write!(f, "InvalidUpdateData"), + PythReceiverError::VaaVerificationFailed => write!(f, "VaaVerificationFailed"), + PythReceiverError::InvalidVaa => write!(f, "InvalidVaa"), + PythReceiverError::InvalidWormholeMessage => write!(f, "InvalidWormholeMessage"), + PythReceiverError::InvalidMerkleProof => write!(f, "InvalidMerkleProof"), + PythReceiverError::InvalidAccumulatorMessage => write!(f, "InvalidAccumulatorMessage"), + PythReceiverError::InvalidMerkleRoot => write!(f, "InvalidMerkleRoot"), + PythReceiverError::InvalidMerklePath => write!(f, "InvalidMerklePath"), + PythReceiverError::InvalidUnknownSource => write!(f, "InvalidUnknownSource"), + PythReceiverError::NewPriceUnavailable => write!(f, "NewPriceUnavailable"), + PythReceiverError::InvalidAccumulatorMessageType => { + write!(f, "InvalidAccumulatorMessageType") + } + PythReceiverError::InsufficientFee => write!(f, "InsufficientFee"), + PythReceiverError::InvalidEmitterAddress => write!(f, "InvalidEmitterAddress"), + PythReceiverError::TooManyUpdates => write!(f, "TooManyUpdates"), + PythReceiverError::PriceFeedNotFoundWithinRange => { + write!(f, "PriceFeedNotFoundWithinRange") + } + PythReceiverError::NoFreshUpdate => write!(f, "NoFreshUpdate"), + PythReceiverError::PriceFeedNotFound => write!(f, "PriceFeedNotFound"), + PythReceiverError::InvalidGovernanceMessage => write!(f, "InvalidGovernanceMessage"), + PythReceiverError::InvalidGovernanceTarget => write!(f, "InvalidGovernanceTarget"), + PythReceiverError::InvalidGovernanceAction => write!(f, "InvalidGovernanceAction"), + PythReceiverError::InvalidGovernanceDataSource => { + write!(f, "InvalidGovernanceDataSource") + } + PythReceiverError::OldGovernanceMessage => write!(f, "OldGovernanceMessage"), + PythReceiverError::GovernanceMessageAlreadyExecuted => { + write!(f, "GovernanceMessageAlreadyExecuted") + } + PythReceiverError::InvalidWormholeAddressToSet => { + write!(f, "InvalidWormholeAddressToSet") + } + PythReceiverError::WormholeUninitialized => { + write!(f, "Wormhole is uninitialized, please set the Wormhole address and initialize the contract first") + } + } + } +} + +impl core::fmt::Display for PythReceiverError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + PythReceiverError::PriceUnavailable => write!(f, "Price unavailable"), + PythReceiverError::InvalidUpdateData => write!(f, "Invalid update data"), + PythReceiverError::VaaVerificationFailed => write!(f, "VAA verification failed"), + PythReceiverError::InvalidVaa => write!(f, "Invalid VAA"), + PythReceiverError::InvalidWormholeMessage => write!(f, "Invalid Wormhole message"), + PythReceiverError::InvalidMerkleProof => write!(f, "Invalid Merkle proof"), + PythReceiverError::InvalidAccumulatorMessage => { + write!(f, "Invalid accumulator message") + } + PythReceiverError::InvalidMerkleRoot => write!(f, "Invalid Merkle root"), + PythReceiverError::InvalidMerklePath => write!(f, "Invalid Merkle path"), + PythReceiverError::InvalidUnknownSource => write!(f, "Invalid unknown source"), + PythReceiverError::NewPriceUnavailable => write!(f, "New price unavailable"), + PythReceiverError::InvalidAccumulatorMessageType => { + write!(f, "Invalid accumulator message type") + } + PythReceiverError::InsufficientFee => write!(f, "Insufficient fee"), + PythReceiverError::InvalidEmitterAddress => write!(f, "Invalid emitter address"), + PythReceiverError::TooManyUpdates => write!(f, "Too many updates"), + PythReceiverError::PriceFeedNotFoundWithinRange => { + write!(f, "Price feed not found within range") + } + PythReceiverError::NoFreshUpdate => write!(f, "No fresh update"), + PythReceiverError::PriceFeedNotFound => write!(f, "Price feed not found"), + PythReceiverError::InvalidGovernanceMessage => write!(f, "Invalid governance message"), + PythReceiverError::InvalidGovernanceTarget => write!(f, "Invalid governance target"), + PythReceiverError::InvalidGovernanceAction => write!(f, "Invalid governance action"), + PythReceiverError::InvalidGovernanceDataSource => { + write!(f, "Invalid governance data source") + } + PythReceiverError::OldGovernanceMessage => write!(f, "Old governance message"), + PythReceiverError::GovernanceMessageAlreadyExecuted => { + write!(f, "Governance message already executed") + } + PythReceiverError::InvalidWormholeAddressToSet => { + write!(f, "Invalid Wormhole address to set") + } + PythReceiverError::WormholeUninitialized => { + write!(f, "Wormhole is uninitialized, please set the Wormhole address and initialize the contract first") + } + } } } @@ -49,6 +143,14 @@ impl From for Vec { PythReceiverError::PriceFeedNotFoundWithinRange => 16, PythReceiverError::NoFreshUpdate => 17, PythReceiverError::PriceFeedNotFound => 18, + PythReceiverError::InvalidGovernanceMessage => 19, + PythReceiverError::InvalidGovernanceTarget => 20, + PythReceiverError::InvalidGovernanceAction => 21, + PythReceiverError::InvalidGovernanceDataSource => 22, + PythReceiverError::OldGovernanceMessage => 23, + PythReceiverError::GovernanceMessageAlreadyExecuted => 24, + PythReceiverError::InvalidWormholeAddressToSet => 25, + PythReceiverError::WormholeUninitialized => 26, }] } } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/governance_structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/governance_structs.rs new file mode 100644 index 0000000000..75ad67cdbf --- /dev/null +++ b/target_chains/stylus/contracts/pyth-receiver/src/governance_structs.rs @@ -0,0 +1,337 @@ +use crate::error::PythReceiverError; +use crate::structs::DataSource; +use alloc::vec::Vec; +use stylus_sdk::alloy_primitives::{Address, FixedBytes, U16}; + +// Magic is `PTGM` encoded as a 4 byte data: Pyth Governance Message +const MAGIC: u32 = 0x5054474d; +const MODULE_TARGET: u8 = 1; + +#[derive(Clone, Debug, PartialEq)] +pub enum GovernanceAction { + UpgradeContract, + AuthorizeGovernanceDataSourceTransfer, + SetDataSources, + SetFee, + SetValidPeriod, + RequestGovernanceDataSourceTransfer, + SetWormholeAddress, + SetTransactionFee, + WithdrawFee, +} + +impl TryFrom for GovernanceAction { + type Error = PythReceiverError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(GovernanceAction::UpgradeContract), + 1 => Ok(GovernanceAction::AuthorizeGovernanceDataSourceTransfer), + 2 => Ok(GovernanceAction::SetDataSources), + 3 => Ok(GovernanceAction::SetFee), + 4 => Ok(GovernanceAction::SetValidPeriod), + 5 => Ok(GovernanceAction::RequestGovernanceDataSourceTransfer), + 6 => Ok(GovernanceAction::SetWormholeAddress), + 8 => Ok(GovernanceAction::SetTransactionFee), + 9 => Ok(GovernanceAction::WithdrawFee), + _ => Err(PythReceiverError::InvalidGovernanceAction), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct GovernanceInstruction { + pub target_chain_id: u16, + pub payload: GovernancePayload, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum GovernancePayload { + UpgradeContract(UpgradeContract), + AuthorizeGovernanceDataSourceTransfer(AuthorizeGovernanceDataSourceTransfer), + SetDataSources(SetDataSources), + SetFee(SetFee), + SetValidPeriod(SetValidPeriod), + RequestGovernanceDataSourceTransfer(RequestGovernanceDataSourceTransfer), + SetWormholeAddress(SetWormholeAddress), + SetTransactionFee(SetTransactionFee), + WithdrawFee(WithdrawFee), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SetFee { + pub value: u64, + pub expo: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SetValidPeriod { + pub valid_time_period_seconds: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SetTransactionFee { + pub value: u64, + pub expo: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct WithdrawFee { + pub value: u64, + pub expo: u64, + pub target_address: Address, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SetDataSources { + pub sources: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SetWormholeAddress { + pub address: Address, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct RequestGovernanceDataSourceTransfer { + pub governance_data_source_index: u32, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AuthorizeGovernanceDataSourceTransfer { + pub claim_vaa: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct UpgradeContract { + pub new_implementation: FixedBytes<32>, +} + +pub fn parse_instruction(payload: Vec) -> Result { + if payload.len() < 8 { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + let mut cursor = 0; + + let magic_bytes = payload + .get(cursor..cursor + 4) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + + let magic = u32::from_be_bytes( + magic_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 4; + + if magic != MAGIC { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + let module = payload[cursor]; + cursor += 1; + + if module != MODULE_TARGET { + return Err(PythReceiverError::InvalidGovernanceTarget); + } + + let action = GovernanceAction::try_from(payload[cursor])?; + cursor += 1; + + let target_chain_id = u16::from_be_bytes([payload[cursor], payload[cursor + 1]]); + cursor += 2; + + let governance_payload = match action { + GovernanceAction::UpgradeContract => { + if payload.len() < cursor + 32 { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + let mut new_implementation = [0u8; 32]; + new_implementation.copy_from_slice(&payload[cursor..cursor + 32]); + cursor += 32; + GovernancePayload::UpgradeContract(UpgradeContract { + new_implementation: FixedBytes::from(new_implementation), + }) + } + GovernanceAction::AuthorizeGovernanceDataSourceTransfer => { + let claim_vaa = payload[cursor..].to_vec(); + cursor = payload.len(); + GovernancePayload::AuthorizeGovernanceDataSourceTransfer( + AuthorizeGovernanceDataSourceTransfer { claim_vaa }, + ) + } + GovernanceAction::RequestGovernanceDataSourceTransfer => { + if payload.len() < cursor + 4 { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + let governance_data_source_bytes = payload + .get(cursor..cursor + 4) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + + let governance_data_source_index = u32::from_be_bytes( + governance_data_source_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 4; + GovernancePayload::RequestGovernanceDataSourceTransfer( + RequestGovernanceDataSourceTransfer { + governance_data_source_index, + }, + ) + } + GovernanceAction::SetDataSources => { + if payload.len() < cursor + 1 { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + let num_sources = payload[cursor]; + cursor += 1; + + let mut sources = Vec::new(); + for _ in 0..num_sources { + if payload.len() < cursor + 34 { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + let emitter_chain_id = u16::from_be_bytes([payload[cursor], payload[cursor + 1]]); + cursor += 2; + + let mut emitter_address = [0u8; 32]; + emitter_address.copy_from_slice(&payload[cursor..cursor + 32]); + cursor += 32; + + sources.push(DataSource { + chain_id: U16::from(emitter_chain_id), + emitter_address: FixedBytes::from(emitter_address), + }); + } + GovernancePayload::SetDataSources(SetDataSources { sources }) + } + GovernanceAction::SetFee => { + if payload.len() < cursor + 16 { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + let fee_value_bytes = payload + .get(cursor..cursor + 8) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + + let value = u64::from_be_bytes( + fee_value_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 8; + + let expo_bytes = payload + .get(cursor..cursor + 8) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + let expo = u64::from_be_bytes( + expo_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 8; + GovernancePayload::SetFee(SetFee { value, expo }) + } + GovernanceAction::SetValidPeriod => { + let valid_period_bytes = payload + .get(cursor..cursor + 8) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + let valid_time_period_seconds = u64::from_be_bytes( + valid_period_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + cursor += 8; + GovernancePayload::SetValidPeriod(SetValidPeriod { + valid_time_period_seconds, + }) + } + GovernanceAction::SetWormholeAddress => { + let address_bytes: &[u8; 20] = payload + .get(cursor..cursor + 20) + .ok_or(PythReceiverError::InvalidGovernanceMessage)? + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?; + cursor += 20; + GovernancePayload::SetWormholeAddress(SetWormholeAddress { + address: Address::from(address_bytes), + }) + } + GovernanceAction::SetTransactionFee => { + let fee_value_bytes = payload + .get(cursor..cursor + 8) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + + let value = u64::from_be_bytes( + fee_value_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 8; + + let expo_bytes = payload + .get(cursor..cursor + 8) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + let expo = u64::from_be_bytes( + expo_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 8; + GovernancePayload::SetTransactionFee(SetTransactionFee { value, expo }) + } + GovernanceAction::WithdrawFee => { + if payload.len() < cursor + 28 { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + let mut target_address_bytes = [0u8; 20]; + target_address_bytes.copy_from_slice(&payload[cursor..cursor + 20]); + cursor += 20; + + let fee_value_bytes = payload + .get(cursor..cursor + 8) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + + let value = u64::from_be_bytes( + fee_value_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 8; + + let expo_bytes = payload + .get(cursor..cursor + 8) + .ok_or(PythReceiverError::InvalidGovernanceMessage)?; + let expo = u64::from_be_bytes( + expo_bytes + .try_into() + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?, + ); + + cursor += 8; + GovernancePayload::WithdrawFee(WithdrawFee { + value, + expo, + target_address: Address::from(target_address_bytes), + }) + } + }; + + if cursor != payload.len() { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + Ok(GovernanceInstruction { + target_chain_id, + payload: governance_payload, + }) +} diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index 33b7c5440e..99d8c9ca0d 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -42,6 +42,7 @@ mod test { Ok(get_total_fee(total_num_updates)) } + #[cfg(test)] fn get_total_fee(total_num_updates: u64) -> U256 { U256::from(total_num_updates).saturating_mul(SINGLE_UPDATE_FEE_IN_WEI) + TRANSACTION_FEE_IN_WEI diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 91730e534d..9fd530f25a 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -6,8 +6,11 @@ extern crate alloc; mod error; +mod governance_structs; #[cfg(test)] mod integration_tests; +#[cfg(test)] +mod pyth_governance_test; mod structs; #[cfg(test)] mod test_data; @@ -18,6 +21,7 @@ use mock_instant::global::MockClock; use alloc::vec::Vec; use stylus_sdk::{ alloy_primitives::{Address, FixedBytes, I32, I64, U16, U256, U32, U64}, + alloy_sol_types::sol, call::Call, prelude::*, storage::{ @@ -27,6 +31,7 @@ use stylus_sdk::{ }; use error::PythReceiverError; +use governance_structs::*; use pythnet_sdk::{ accumulators::merkle::{MerklePath, MerkleRoot}, hashers::keccak256_160::Keccak160, @@ -42,13 +47,23 @@ use pythnet_sdk::{ use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceFeedStorage, PriceReturn}; use wormhole_vaas::{Readable, Vaa, Writeable}; +sol! { + event FeeSet(uint256 indexed old_fee, uint256 indexed new_fee); + event TransactionFeeSet(uint256 indexed old_fee, uint256 indexed new_fee); + event FeeWithdrawn(address indexed target_address, uint256 fee_amount); + event ValidPeriodSet(uint256 indexed old_valid_period, uint256 indexed new_valid_period); + event DataSourcesSet(bytes32[] old_data_sources, bytes32[] new_data_sources); + event GovernanceDataSourceSet(uint16 old_chain_id, bytes32 old_emitter_address, uint16 new_chain_id, bytes32 new_emitter_address, uint64 initial_sequence); +} + sol_interface! { - interface IWormholeContract { - function initialize(address[] memory initial_guardians, uint16 chain_id, uint16 governance_chain_id, address governance_contract) external; - function getGuardianSet(uint32 index) external view returns (uint8[] memory); - function parseAndVerifyVm(uint8[] memory encoded_vaa) external view returns (uint8[] memory); - function quorum(uint32 num_guardians) external pure returns (uint32); - } + interface IWormholeContract { + function initialize(address[] memory initial_guardians, uint32 initial_guardian_set_index, uint16 chain_id, uint16 governance_chain_id, address governance_contract) external; + function getGuardianSet(uint32 index) external view returns (uint8[] memory); + function parseAndVerifyVm(uint8[] memory encoded_vaa) external view returns (uint8[] memory); + function quorum(uint32 num_guardians) external pure returns (uint32); + function chainId() external view returns (uint16); +} } #[storage] @@ -516,6 +531,70 @@ impl PythReceiver { price_feeds } + pub fn execute_governance_instruction( + &mut self, + data: Vec, + ) -> Result<(), PythReceiverError> { + let wormhole: IWormholeContract = IWormholeContract::new(self.wormhole.get()); + let config = Call::new(); + + wormhole + .parse_and_verify_vm(config, Vec::from(data.clone())) + .map_err(|_| PythReceiverError::InvalidWormholeMessage)?; + + let vm = Vaa::read(&mut Vec::from(data.clone()).as_slice()) + .map_err(|_| PythReceiverError::InvalidVaa)?; + + verify_governance_vm(self, vm.clone())?; + + let instruction = governance_structs::parse_instruction(vm.body.payload.to_vec()) + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?; + + let chain_id_config = Call::new(); + + let wormhole_id = wormhole + .chain_id(chain_id_config) + .map_err(|_| PythReceiverError::WormholeUninitialized)?; + + if instruction.target_chain_id != 0 && instruction.target_chain_id != wormhole_id { + return Err(PythReceiverError::InvalidGovernanceTarget); + } + + match instruction.payload { + GovernancePayload::SetFee(payload) => { + self.set_fee(payload.value, payload.expo); + } + GovernancePayload::SetDataSources(payload) => { + set_data_sources(self, payload.sources); + } + GovernancePayload::SetWormholeAddress(payload) => { + self.set_wormhole_address(payload.address, data.clone())?; + } + GovernancePayload::RequestGovernanceDataSourceTransfer(_) => { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + GovernancePayload::AuthorizeGovernanceDataSourceTransfer(payload) => { + self.authorize_governance_transfer(payload.claim_vaa)?; + } + GovernancePayload::UpgradeContract(_payload) => {} + GovernancePayload::SetValidPeriod(payload) => { + self.set_valid_period(payload.valid_time_period_seconds); + } + GovernancePayload::SetTransactionFee(payload) => { + self.set_transaction_fee(payload.value, payload.expo); + } + GovernancePayload::WithdrawFee(payload) => { + self.withdraw_fee(payload.value, payload.expo, payload.target_address)?; + } + } + + Ok(()) + } + + fn upgrade_contract(&self, _new_implementation: FixedBytes<32>) { + unimplemented!("Upgrade contract not yet implemented"); + } + fn is_no_older_than(&self, publish_time: U64, max_age: u64) -> bool { self.get_current_timestamp() .saturating_sub(publish_time.to::()) @@ -534,6 +613,209 @@ impl PythReceiver { self.vm().block_timestamp() } } + + fn set_fee(&mut self, value: u64, expo: u64) { + let new_fee = U256::from(value).saturating_mul(U256::from(10).pow(U256::from(expo))); + let old_fee = self.single_update_fee_in_wei.get(); + + self.single_update_fee_in_wei.set(new_fee); + + log(self.vm(), FeeSet { old_fee, new_fee }); + } + + fn set_valid_period(&mut self, valid_time_period_seconds: u64) { + let old_valid_period = self.valid_time_period_seconds.get(); + let new_valid_period = U256::from(valid_time_period_seconds); + self.valid_time_period_seconds.set(new_valid_period); + + log( + self.vm(), + ValidPeriodSet { + old_valid_period, + new_valid_period, + }, + ); + } + + fn set_wormhole_address( + &mut self, + address: Address, + data: Vec, + ) -> Result<(), PythReceiverError> { + let wormhole: IWormholeContract = IWormholeContract::new(address); + let config = Call::new(); + + // Make sure the contract at the new Wormhole address is initialized and functional by testing it on the SetWormholeAddress instruction VAA + wormhole + .parse_and_verify_vm(config, data.clone()) + .map_err(|_| PythReceiverError::InvalidVaa)?; + + let vm = Vaa::read(&mut data.as_slice()) + .map_err(|_| PythReceiverError::VaaVerificationFailed)?; + + if vm.body.emitter_chain != self.governance_data_source_chain_id.get().to::() { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + if vm.body.emitter_address.as_slice() + != self.governance_data_source_emitter_address.get().as_slice() + { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + if vm.body.sequence.to::() <= self.last_executed_governance_sequence.get().to::() + { + return Err(PythReceiverError::InvalidWormholeAddressToSet); + } + + let data = governance_structs::parse_instruction(vm.body.payload.to_vec()) + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?; + + match data.payload { + GovernancePayload::SetWormholeAddress(payload) => { + if payload.address != address { + return Err(PythReceiverError::InvalidWormholeAddressToSet); + } + } + _ => return Err(PythReceiverError::InvalidGovernanceMessage), + } + + self.wormhole.set(address); + Ok(()) + } + + fn authorize_governance_transfer( + &mut self, + claim_vaa: Vec, + ) -> Result<(), PythReceiverError> { + let wormhole: IWormholeContract = IWormholeContract::new(self.wormhole.get()); + let config = Call::new(); + wormhole + .parse_and_verify_vm(config, claim_vaa.clone()) + .map_err(|_| PythReceiverError::InvalidWormholeMessage)?; + + let claim_vm = Vaa::read(&mut Vec::from(claim_vaa).as_slice()) + .map_err(|_| PythReceiverError::VaaVerificationFailed)?; + + let instruction = governance_structs::parse_instruction(claim_vm.body.payload.to_vec()) + .map_err(|_| PythReceiverError::InvalidGovernanceMessage)?; + + let config2 = Call::new(); + if instruction.target_chain_id != 0 + && instruction.target_chain_id != wormhole.chain_id(config2).unwrap_or(0) + { + return Err(PythReceiverError::InvalidGovernanceTarget); + } + + let request_payload = match instruction.payload { + GovernancePayload::RequestGovernanceDataSourceTransfer(payload) => payload, + _ => return Err(PythReceiverError::InvalidGovernanceMessage), + }; + + let current_index = self.governance_data_source_index.get().to::(); + let new_index = request_payload.governance_data_source_index; + + if current_index >= new_index { + return Err(PythReceiverError::OldGovernanceMessage); + } + + self.governance_data_source_index.set(U32::from(new_index)); + let old_data_source_emitter_address = self.governance_data_source_emitter_address.get(); + + self.governance_data_source_chain_id + .set(U16::from(claim_vm.body.emitter_chain)); + let emitter_bytes: [u8; 32] = claim_vm + .body + .emitter_address + .as_slice() + .try_into() + .map_err(|_| PythReceiverError::InvalidEmitterAddress)?; + self.governance_data_source_emitter_address + .set(FixedBytes::from(emitter_bytes)); + + let last_executed_governance_sequence = claim_vm.body.sequence.to::(); + self.last_executed_governance_sequence + .set(U64::from(last_executed_governance_sequence)); + + log( + self.vm(), + GovernanceDataSourceSet { + old_chain_id: current_index as u16, + old_emitter_address: old_data_source_emitter_address, + new_chain_id: claim_vm.body.emitter_chain, + new_emitter_address: FixedBytes::from(emitter_bytes), + initial_sequence: last_executed_governance_sequence, + }, + ); + + Ok(()) + } + + fn set_transaction_fee(&mut self, value: u64, expo: u64) { + let new_fee = U256::from(value).saturating_mul(U256::from(10).pow(U256::from(expo))); + let old_fee = self.transaction_fee_in_wei.get(); + + self.transaction_fee_in_wei.set(new_fee); + + log(self.vm(), TransactionFeeSet { old_fee, new_fee }); + } + + fn withdraw_fee( + &mut self, + value: u64, + expo: u64, + target_address: Address, + ) -> Result<(), PythReceiverError> { + let fee_to_withdraw = + U256::from(value).saturating_mul(U256::from(10).pow(U256::from(expo))); + let current_balance = self.vm().balance(self.vm().contract_address()); + + if current_balance < fee_to_withdraw { + return Err(PythReceiverError::InsufficientFee); + } + + self.vm() + .transfer_eth(target_address, fee_to_withdraw) + .map_err(|_| PythReceiverError::InsufficientFee)?; + + log( + self.vm(), + FeeWithdrawn { + target_address, + fee_amount: fee_to_withdraw, + }, + ); + + Ok(()) + } +} + +fn verify_governance_vm(receiver: &mut PythReceiver, vm: Vaa) -> Result<(), PythReceiverError> { + if vm.body.emitter_chain != receiver.governance_data_source_chain_id.get().to::() { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + if vm.body.emitter_address.as_slice() + != receiver + .governance_data_source_emitter_address + .get() + .as_slice() + { + return Err(PythReceiverError::InvalidGovernanceMessage); + } + + let current_sequence = vm.body.sequence.to::(); + let last_executed_sequence = receiver.last_executed_governance_sequence.get().to::(); + + if current_sequence <= last_executed_sequence { + return Err(PythReceiverError::GovernanceMessageAlreadyExecuted); + } + + receiver + .last_executed_governance_sequence + .set(U64::from(current_sequence)); + + Ok(()) } fn parse_wormhole_proof(vaa: Vaa) -> Result, PythReceiverError> { @@ -544,3 +826,39 @@ fn parse_wormhole_proof(vaa: Vaa) -> Result, PythReceiverE }); Ok(root) } + +fn set_data_sources(receiver: &mut PythReceiver, data_sources: Vec) { + let mut old_data_sources = Vec::new(); + for i in 0..receiver.valid_data_sources.len() { + if let Some(storage_data_source) = receiver.valid_data_sources.get(i) { + let data_source = DataSource { + chain_id: storage_data_source.chain_id.get(), + emitter_address: storage_data_source.emitter_address.get(), + }; + old_data_sources.push(data_source.emitter_address); + receiver.is_valid_data_source.setter(data_source).set(false); + } + } + + receiver.valid_data_sources.erase(); + + let mut new_data_sources = Vec::new(); + for data_source in data_sources { + let mut storage_data_source = receiver.valid_data_sources.grow(); + storage_data_source.chain_id.set(data_source.chain_id); + storage_data_source + .emitter_address + .set(data_source.emitter_address); + + new_data_sources.push(data_source.emitter_address); + receiver.is_valid_data_source.setter(data_source).set(true); + } + + log( + receiver.vm(), + DataSourcesSet { + old_data_sources, + new_data_sources, + }, + ); +} diff --git a/target_chains/stylus/contracts/pyth-receiver/src/pyth_governance_test.rs b/target_chains/stylus/contracts/pyth-receiver/src/pyth_governance_test.rs new file mode 100644 index 0000000000..8dc918b4b0 --- /dev/null +++ b/target_chains/stylus/contracts/pyth-receiver/src/pyth_governance_test.rs @@ -0,0 +1,352 @@ +#[cfg(test)] +mod test { + use crate::{DataSourcesSet, FeeSet, GovernanceDataSourceSet, PythReceiver, TransactionFeeSet}; + use alloy_primitives::{address, Address, FixedBytes, U256}; + use hex::FromHex; + use motsu::prelude::*; + use wormhole_contract::WormholeContract; + + const PYTHNET_CHAIN_ID: u16 = 26; + const PYTHNET_EMITTER_ADDRESS: [u8; 32] = [ + 0xe1, 0x01, 0xfa, 0xed, 0xac, 0x58, 0x51, 0xe3, 0x2b, 0x9b, 0x23, 0xb5, 0xf9, 0x41, 0x1a, + 0x8c, 0x2b, 0xac, 0x4a, 0xae, 0x3e, 0xd4, 0xdd, 0x7b, 0x81, 0x1d, 0xd1, 0xa7, 0x2e, 0xa4, + 0xaa, 0x71, + ]; + + const CHAIN_ID: u16 = 2; + const GOVERNANCE_CONTRACT: U256 = U256::from_limbs([4, 0, 0, 0]); + + const SINGLE_UPDATE_FEE_IN_WEI: U256 = U256::from_limbs([100, 0, 0, 0]); + const TRANSACTION_FEE_IN_WEI: U256 = U256::from_limbs([32, 0, 0, 0]); + + const TEST_SIGNER1: Address = Address::new([ + 0xbe, 0xFA, 0x42, 0x9d, 0x57, 0xcD, 0x18, 0xb7, 0xF8, 0xA4, 0xd9, 0x1A, 0x2d, 0xa9, 0xAB, + 0x4A, 0xF0, 0x5d, 0x0F, 0xBe, + ]); + const TEST_SIGNER2: Address = Address::new([ + 0x4b, 0xa0, 0xC2, 0xdb, 0x9A, 0x26, 0x20, 0x8b, 0x3b, 0xB1, 0xa5, 0x0B, 0x01, 0xb1, 0x69, + 0x41, 0xc1, 0x0D, 0x76, 0xdb, + ]); + const GOVERNANCE_CHAIN_ID: u16 = 1; + const GOVERNANCE_EMITTER: [u8; 32] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x11, + ]; + const TEST_PYTH2_WORMHOLE_CHAIN_ID: u16 = 1; + const TEST_PYTH2_WORMHOLE_EMITTER: [u8; 32] = [ + 0x71, 0xf8, 0xdc, 0xb8, 0x63, 0xd1, 0x76, 0xe2, 0xc4, 0x20, 0xad, 0x66, 0x10, 0xcf, 0x68, + 0x73, 0x59, 0x61, 0x2b, 0x6f, 0xb3, 0x92, 0xe0, 0x64, 0x2b, 0x0c, 0xa6, 0xb1, 0xf1, 0x86, + 0xaa, 0x3b, + ]; + const TARGET_CHAIN_ID: u16 = 2; + + #[cfg(test)] + fn pyth_wormhole_init( + pyth_contract: &Contract, + wormhole_contract: &Contract, + alice: &Address, + guardian_set_index: u32, + ) { + let guardians = vec![address!("0x7e5f4552091a69125d5dfcb7b8c2659029395bdf")]; + + let governance_contract = + Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); + wormhole_contract + .sender(*alice) + .initialize( + guardians, + guardian_set_index, + CHAIN_ID, + GOVERNANCE_CHAIN_ID, + governance_contract, + ) + .unwrap(); + + let single_update_fee = SINGLE_UPDATE_FEE_IN_WEI; + let valid_time_period = U256::from(3600u64); + + let data_source_chain_ids = vec![PYTHNET_CHAIN_ID]; + let data_source_emitter_addresses = vec![PYTHNET_EMITTER_ADDRESS]; + + let governance_chain_id = 1u16; + let governance_initial_sequence = 0u64; + + pyth_contract.sender(*alice).initialize( + wormhole_contract.address(), + single_update_fee, + valid_time_period, + data_source_chain_ids, + data_source_emitter_addresses, + governance_chain_id, + GOVERNANCE_EMITTER, + governance_initial_sequence, + ); + } + + #[motsu::test] + fn test_set_data_sources( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let hex_str = "0100000000010069825ef00344cf745b6e72a41d4f869d4e90de517849360c72bf94efc97681671d826e484747b21a80c8f1e7816021df9f55e458a6e7a717cb2bd2a1e85fd57100499602d200000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d010200020100010000000000000000000000000000000000000000000000000000000000001111"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + assert!(result.is_ok()); + + let expected_event = DataSourcesSet { + old_data_sources: vec![FixedBytes::from(PYTHNET_EMITTER_ADDRESS)], + new_data_sources: vec![FixedBytes::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x11, + ])], + }; + assert!( + pyth_contract.emitted(&expected_event), + "DataSourcesSet event should be emitted" + ); + + let result2 = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + assert!( + result2.is_err(), + "Second execution should fail due to sequence number check" + ); + } + + #[motsu::test] + fn test_set_valid_period( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let hex_str = "010000000001006fc16df905b08c16553eda9d5a7898ec7eba4267ce0af7945625c955e8f435fc7df7a4087af360f88c2477f0c2f4e7eaa4bb1e8fd43677f4d6b04ee20e225186000000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d010400020000000000000000"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + + println!("Result: {:?}", result.unwrap_err()); + // assert!(result.is_ok()); + } + + #[motsu::test] + fn test_set_fee( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let hex_str = "0100000000010057940f58a6a44c93606bd721701539e0da93d5ea1583a735fbb13ecbcf9c01fc70240de519ea76869af14d067d68c5f3f2230f565f41b7009f3c3e63749353ed000000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d0103000200000000000000050000000000000003"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + + assert!(result.is_ok()); + + let expected_new_fee = U256::from(5000); + let expected_event = FeeSet { + old_fee: SINGLE_UPDATE_FEE_IN_WEI, + new_fee: expected_new_fee, + }; + assert!( + pyth_contract.emitted(&expected_event), + "FeeSet event should be emitted" + ); + + let result2 = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + assert!( + result2.is_err(), + "Second execution should fail due to sequence number check" + ); + } + + #[motsu::test] + fn test_set_fee_in_token( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let hex_str = "0100000000010051c35e992b6dcfc81f02b430914694b4fbbeae7f952f3d3f1ff350f332d1d24916e2f56336ce0c392247e8c1fbb1b74eac87a68d681c729fa860f3788ece2788000000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d0107000200000000000000050000000000000003147e5f4552091a69125d5dfcb7b8c2659029395bdf"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + if result.is_err() { + println!("Error: {:?}", result.as_ref().unwrap_err()); + } + assert!(result.is_ok()); + + let result2 = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + assert!( + result2.is_err(), + "Second execution should fail due to sequence number check" + ); + } + + // This test is commented out because it requires an already deployed new Wormhole contract. + // This function demonstrates the usage of this instruction, however. + /* + #[motsu::test] + fn test_set_wormhole_address( + pyth_contract: Contract, + wormhole_contract: Contract, + wormhole_contract_2: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let guardians = vec![address!("0x7e5f4552091a69125d5dfcb7b8c2659029395bdf")]; + let governance_contract = + Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); + wormhole_contract_2 + .sender(alice) + .initialize( + guardians, + 0, + CHAIN_ID, + GOVERNANCE_CHAIN_ID, + governance_contract, + ) + .unwrap(); + + + + let hex_str = format!("010000000001001daf08e5e3799cbc6096a90c2361e43220325418f377620a7a73d6bece18322679f6ada9725d9081743805efb8bccecd51098f1d76f34cba8b835fae643bbd9c000000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d01060002{:040x}", wormhole_contract_2.address()); + let bytes = Vec::from_hex(&hex_str).expect("Invalid hex string"); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + if result.is_err() { + println!( + "SetWormholeAddress Error: {:?}", + result.as_ref().unwrap_err() + ); + } + */ + + #[motsu::test] + fn test_authorize_governance_data_source_transfer( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let hex_str = "01000000000100b441e497034be4ee82242a866461d5e6744082654f71301a96f579f629b6bf176cc0c1964cd7d4f792436b7a73fc7024d72b138869b4d81d449740bb08148238000000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d01010002010000000001009c9dc62e92fefe0806dce30b662a5d319417a62dccc700b5f2678306d39c005f7a5e74d11df287301d85d328a3d000c5d793c57161f3150c7eb1a17668946e6b010000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000064005054474d0105000200000000"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + if result.is_err() { + println!( + "AuthorizeGovernanceDataSourceTransfer Error: {:?}", + result.as_ref().unwrap_err() + ); + } + assert!(result.is_ok()); + + let expected_event = GovernanceDataSourceSet { + old_chain_id: 0, // Initial governance_data_source_index + old_emitter_address: FixedBytes::from(GOVERNANCE_EMITTER), // Initial governance emitter from pyth_wormhole_init + new_chain_id: 1, // claim_vm.body.emitter_chain from the VAA + new_emitter_address: FixedBytes::from(GOVERNANCE_EMITTER), // emitter_bytes from the VAA + initial_sequence: 100, // claim_vm.body.sequence from the VAA (0x64 = 100) + }; + assert!( + pyth_contract.emitted(&expected_event), + "GovernanceDataSourceSet event should be emitted" + ); + } + + #[motsu::test] + fn test_set_transaction_fee( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let hex_str = "010000000001001554008232e74cb3ac74acc4527ead8a39637c537ec9b3d1fbb624c1f4f52e341e24ae89d978e033f5345e4af244df0ec61f380d9e33330f439d2b6764850270010000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d0108000200000000000000640000000000000003"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + if result.is_err() { + println!( + "SetTransactionFee Error: {:?}", + result.as_ref().unwrap_err() + ); + } + assert!(result.is_ok()); + + let expected_new_fee = U256::from(100000); + let expected_event = TransactionFeeSet { + old_fee: U256::ZERO, + new_fee: expected_new_fee, + }; + assert!( + pyth_contract.emitted(&expected_event), + "TransactionFeeSet event should be emitted" + ); + + let result2 = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + assert!( + result2.is_err(), + "Second execution should fail due to sequence number check" + ); + } + + // Fee transfers can't be done in the motsu testing framework. This commented test serves as an example for how to use the function, though. + + /* + #[motsu::test] + fn test_withdraw_fee( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice, 0); + + let hex_str = "0100000000010030f48904e130d76ee219bc59988f89526e5c9860e89efda3a74e33c3ab53d4e6036d1c67249d2f25a27e8c94d203609785839e3e4817d0a03214ea8bbf6a8415000000000100000000000100000000000000000000000000000000000000000000000000000000000000110000000000000001005054474d0109000270997970c51812dc3a010c7d01b50e0d17dc79c800000000000000640000000000000003"; + let bytes = Vec::from_hex(&hex_str).expect("Invalid hex string"); + + pyth_contract.address().fund(U256::from(200000u64)); + + let result = pyth_contract + .sender(alice) + .execute_governance_instruction(bytes.clone()); + + if result.is_err() { + println!("WithdrawFee Error: {:?}", result.as_ref().unwrap_err()); + } + assert!(result.is_ok()); + } + */ +} diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index a6bfdaa448..90504a6cc8 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -19,6 +19,13 @@ pub struct DataSourceStorage { pub emitter_address: StorageFixedBytes<32>, } +impl Erase for DataSourceStorage { + fn erase(&mut self) { + self.chain_id.erase(); + self.emitter_address.erase(); + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct DataSource { pub chain_id: U16, diff --git a/target_chains/stylus/contracts/wormhole/src/lib.rs b/target_chains/stylus/contracts/wormhole/src/lib.rs index ce638e0580..1d23eb2282 100644 --- a/target_chains/stylus/contracts/wormhole/src/lib.rs +++ b/target_chains/stylus/contracts/wormhole/src/lib.rs @@ -1,6 +1,5 @@ #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] #![cfg_attr(not(any(test, feature = "export-abi")), no_std)] - #![macro_use] extern crate alloc; @@ -9,9 +8,9 @@ static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; use alloc::{vec, vec::Vec}; use stylus_sdk::{ - prelude::*, - storage::{StorageMap, StorageUint, StorageAddress, StorageBool}, alloy_primitives::{Address, FixedBytes, U256, keccak256}, + prelude::*, + storage::{StorageAddress, StorageBool, StorageMap, StorageUint}, }; use k256::ecdsa::{RecoveryId, Signature, VerifyingKey}; @@ -125,9 +124,11 @@ impl WormholeContract { if self.initialized.get() { return Err(WormholeError::AlreadyInitialized.into()); } - self.current_guardian_set_index.set(U256::from(initial_guardian_set_index)); + self.current_guardian_set_index + .set(U256::from(initial_guardian_set_index)); self.chain_id.set(U256::from(chain_id)); - self.governance_chain_id.set(U256::from(governance_chain_id)); + self.governance_chain_id + .set(U256::from(governance_chain_id)); self.governance_contract.set(governance_contract); self.store_gs(initial_guardian_set_index, initial_guardians, 0)?; @@ -148,7 +149,7 @@ impl WormholeContract { encoded.extend_from_slice(address.as_slice()); } Ok(encoded) - }, + } Err(e) => Err(e.into()), } } @@ -172,6 +173,9 @@ impl WormholeContract { fn quorum(num_guardians: u32) -> u32 { (num_guardians * 2) / 3 + 1 } + fn chain_id(&self) -> u16 { + self.chain_id.get().try_into().unwrap_or(0u16) + } } impl WormholeContract { @@ -185,118 +189,131 @@ impl WormholeContract { if encoded_vaa.len() < 6 { return Err(WormholeError::InvalidVAAFormat); } - + let mut cursor = 0; - + // Get version - let version = encoded_vaa.get(cursor) + let version = encoded_vaa + .get(cursor) .ok_or(WormholeError::InvalidVAAFormat)?; cursor += 1; - + if *version != 1 { return Err(WormholeError::InvalidVAAFormat); } - + // Get guardian set index - let gsi_bytes = encoded_vaa.get(cursor..cursor + 4) + let gsi_bytes = encoded_vaa + .get(cursor..cursor + 4) .ok_or(WormholeError::InvalidVAAFormat)?; let guardian_set_index = u32::from_be_bytes( - gsi_bytes.try_into() - .map_err(|_| WormholeError::InvalidVAAFormat)? + gsi_bytes + .try_into() + .map_err(|_| WormholeError::InvalidVAAFormat)?, ); cursor += 4; - + // Get number of signatures - let len_signatures = *encoded_vaa.get(cursor) + let len_signatures = *encoded_vaa + .get(cursor) .ok_or(WormholeError::InvalidVAAFormat)?; cursor += 1; - + if len_signatures > 19 { return Err(WormholeError::InvalidVAAFormat); } - + let mut signatures = Vec::with_capacity(len_signatures as usize); - + for _ in 0..len_signatures { if cursor + 66 > encoded_vaa.len() { return Err(WormholeError::InvalidVAAFormat); } - - let guardian_index = *encoded_vaa.get(cursor) + + let guardian_index = *encoded_vaa + .get(cursor) .ok_or(WormholeError::InvalidVAAFormat)?; cursor += 1; - - let sig_bytes = encoded_vaa.get(cursor..cursor + 65) + + let sig_bytes = encoded_vaa + .get(cursor..cursor + 65) .ok_or(WormholeError::InvalidVAAFormat)?; let mut fixed_sig = [0u8; 65]; fixed_sig.copy_from_slice(sig_bytes); cursor += 65; - + signatures.push(GuardianSignature { guardian_index, signature: FixedBytes::from(fixed_sig), }); } - + if cursor + 51 > encoded_vaa.len() { return Err(WormholeError::InvalidVAAFormat); } - + // Get timestamp - let ts_bytes = encoded_vaa.get(cursor..cursor + 4) + let ts_bytes = encoded_vaa + .get(cursor..cursor + 4) .ok_or(WormholeError::InvalidVAAFormat)?; let timestamp = u32::from_be_bytes( - ts_bytes.try_into() - .map_err(|_| WormholeError::InvalidVAAFormat)? + ts_bytes + .try_into() + .map_err(|_| WormholeError::InvalidVAAFormat)?, ); cursor += 4; - + // Get nonce - let nonce_bytes = encoded_vaa.get(cursor..cursor + 4) + let nonce_bytes = encoded_vaa + .get(cursor..cursor + 4) .ok_or(WormholeError::InvalidVAAFormat)?; let nonce = u32::from_be_bytes( - nonce_bytes.try_into() - .map_err(|_| WormholeError::InvalidVAAFormat)? + nonce_bytes + .try_into() + .map_err(|_| WormholeError::InvalidVAAFormat)?, ); cursor += 4; - + // Get emitter chain ID - let emitter_chain_bytes = encoded_vaa.get(cursor..cursor + 2) + let emitter_chain_bytes = encoded_vaa + .get(cursor..cursor + 2) .ok_or(WormholeError::InvalidVAAFormat)?; - let emitter_chain_id = u16::from_be_bytes([ - emitter_chain_bytes[0], - emitter_chain_bytes[1], - ]); + let emitter_chain_id = u16::from_be_bytes([emitter_chain_bytes[0], emitter_chain_bytes[1]]); cursor += 2; - + // Get emitter address - let emitter_address_bytes = encoded_vaa.get(cursor..cursor + 32) + let emitter_address_bytes = encoded_vaa + .get(cursor..cursor + 32) .ok_or(WormholeError::InvalidVAAFormat)?; let mut fixed_emitter = [0u8; 32]; fixed_emitter.copy_from_slice(emitter_address_bytes); cursor += 32; - + // Get sequence - let sequence_bytes = encoded_vaa.get(cursor..cursor + 8) + let sequence_bytes = encoded_vaa + .get(cursor..cursor + 8) .ok_or(WormholeError::InvalidVAAFormat)?; let sequence = u64::from_be_bytes( - sequence_bytes.try_into() - .map_err(|_| WormholeError::InvalidVAAFormat)? + sequence_bytes + .try_into() + .map_err(|_| WormholeError::InvalidVAAFormat)?, ); cursor += 8; - + // Get consistency level - let consistency_level = *encoded_vaa.get(cursor) + let consistency_level = *encoded_vaa + .get(cursor) .ok_or(WormholeError::InvalidVAAFormat)?; cursor += 1; - + // Get payload - let payload = encoded_vaa.get(cursor..) + let payload = encoded_vaa + .get(cursor..) .ok_or(WormholeError::InvalidVAAFormat)? .to_vec(); - + let hash = keccak256(&encoded_vaa[cursor - 51..]); - + Ok(VerifiedVM { version: *version, guardian_set_index, @@ -311,20 +328,26 @@ impl WormholeContract { hash, }) } - fn verify_vm(&self, vaa: &VerifiedVM) -> Result<(), WormholeError> { let guardian_set = self.get_gs_internal(vaa.guardian_set_index)?; - if vaa.guardian_set_index != self.current_guardian_set_index.get().try_into().unwrap_or(0u32) - && guardian_set.expiration_time > 0 { - return Err(WormholeError::GuardianSetExpired) + let current_gsi = self.current_guardian_set_index.get().try_into().unwrap_or(0u32); + if vaa.guardian_set_index != current_gsi && guardian_set.expiration_time > 0 { + return Err(WormholeError::GuardianSetExpired); } - - let num_guardians : u32 = guardian_set.keys.len().try_into().map_err(|_| WormholeError::InvalidInput)?; + let num_guardians: u32 = guardian_set + .keys + .len() + .try_into() + .map_err(|_| WormholeError::InvalidInput)?; let required_signatures = Self::quorum(num_guardians); - let num_signatures : u32 = vaa.signatures.len().try_into().map_err(|_| WormholeError::InvalidInput)?; + let num_signatures: u32 = vaa + .signatures + .len() + .try_into() + .map_err(|_| WormholeError::InvalidInput)?; if num_signatures < required_signatures { return Err(WormholeError::InsufficientSignatures); @@ -353,7 +376,7 @@ impl WormholeContract { let hashed_vaa_hash: FixedBytes<32> = FixedBytes::from(keccak256(vaa.hash)); match self.verify_signature(&hashed_vaa_hash, &signature.signature, guardian_address) { - Ok(true) => {}, + Ok(true) => {} Ok(false) => return Err(WormholeError::InvalidSignature.into()), Err(e) => return Err(e), } @@ -367,16 +390,26 @@ impl WormholeContract { U256::from_be_bytes(keccak256(&key_data).0) } - fn store_gs(&mut self, set_index: u32, guardians: Vec
, expiration_time: u32) -> Result<(), WormholeError> { + fn store_gs( + &mut self, + set_index: u32, + guardians: Vec
, + expiration_time: u32, + ) -> Result<(), WormholeError> { if guardians.is_empty() { return Err(WormholeError::InvalidInput); } - self.guardian_set_sizes.setter(U256::from(set_index)).set(U256::from(guardians.len())); - self.guardian_set_expiry.setter(U256::from(set_index)).set(U256::from(expiration_time)); + self.guardian_set_sizes + .setter(U256::from(set_index)) + .set(U256::from(guardians.len())); + self.guardian_set_expiry + .setter(U256::from(set_index)) + .set(U256::from(expiration_time)); for (i, guardian) in guardians.iter().enumerate() { - let i_u8: u8 = i.try_into() + let i_u8: u8 = i + .try_into() .map_err(|_| WormholeError::InvalidGuardianIndex)?; let key = self.compute_gs_key(set_index, i_u8); self.guardian_keys.setter(key).set(*guardian); @@ -400,27 +433,33 @@ impl WormholeContract { RecoveryId::try_from(recovery_id_byte - 27) .map_err(|_| WormholeError::InvalidSignature)? } else { - RecoveryId::try_from(recovery_id_byte) - .map_err(|_| WormholeError::InvalidSignature)? + RecoveryId::try_from(recovery_id_byte).map_err(|_| WormholeError::InvalidSignature)? }; - let sig = Signature::try_from(&signature[..64]) - .map_err(|_| WormholeError::InvalidSignature)?; + let sig = + Signature::try_from(&signature[..64]).map_err(|_| WormholeError::InvalidSignature)?; - let verifying_key = VerifyingKey::recover_from_prehash(hash.as_slice().try_into().map_err(|_| WormholeError::InvalidInput)?, &sig, recovery_id) - .map_err(|_| WormholeError::InvalidSignature)?; + let verifying_key = VerifyingKey::recover_from_prehash( + hash.as_slice() + .try_into() + .map_err(|_| WormholeError::InvalidInput)?, + &sig, + recovery_id, + ) + .map_err(|_| WormholeError::InvalidSignature)?; let public_key_bytes = verifying_key.to_encoded_point(false); let public_key_slice = &public_key_bytes.as_bytes()[1..]; let address_hash = keccak256(public_key_slice); - let address_bytes: [u8; 20] = address_hash[12..].try_into() + let address_bytes: [u8; 20] = address_hash[12..] + .try_into() .map_err(|_| WormholeError::InvalidAddressLength)?; Ok(Address::from(address_bytes) == guardian_address) } - fn get_gs_internal(&self, index: u32) -> Result { + fn get_gs_internal(&self, index: u32) -> Result { let size = self.guardian_set_sizes.getter(U256::from(index)).get(); if size.is_zero() { return Err(WormholeError::InvalidGuardianSetIndex); @@ -457,6 +496,7 @@ impl IWormhole for WormholeContract { let vaa = self.parse_vm(&encoded_vaa)?; self.verify_vm(&vaa)?; + Ok(vaa) } @@ -465,14 +505,16 @@ impl IWormhole for WormholeContract { } fn get_current_guardian_set_index(&self) -> u32 { - self.current_guardian_set_index.get().try_into().unwrap_or(0u32) + self.current_guardian_set_index + .get() + .try_into() + .unwrap_or(0u32) } fn governance_action_is_consumed(&self, hash: Vec) -> bool { self.consumed_governance_actions.get(hash) } - #[inline] fn chain_id(&self) -> u16 { self.chain_id.get().try_into().unwrap_or(0u16) } @@ -502,10 +544,10 @@ mod tests { use k256::ecdsa::SigningKey; use stylus_sdk::alloy_primitives::keccak256; - #[cfg(test)] - use base64::engine::general_purpose; #[cfg(test)] use base64::Engine; + #[cfg(test)] + use base64::engine::general_purpose; const CHAIN_ID: u16 = 60051; const GOVERNANCE_CHAIN_ID: u16 = 1; @@ -518,9 +560,7 @@ mod tests { #[cfg(test)] fn create_vaa_bytes(input_string: &str) -> Vec { - let vaa_bytes = general_purpose::STANDARD - .decode(input_string) - .unwrap(); + let vaa_bytes = general_purpose::STANDARD.decode(input_string).unwrap(); let vaa: Vec = vaa_bytes; vaa } @@ -528,20 +568,18 @@ mod tests { #[cfg(test)] fn test_guardian_secret1() -> [u8; 32] { [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, + 0x1d, 0x1e, 0x1f, 0x20, ] } #[cfg(test)] fn test_guardian_secret2() -> [u8; 32] { [ - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, + 0x3d, 0x3e, 0x3f, 0x40, ] } @@ -596,8 +634,6 @@ mod tests { ] } - - #[cfg(test)] fn test_guardian_address1() -> Address { let secret = test_guardian_secret1(); @@ -610,7 +646,6 @@ mod tests { Address::from(address_bytes) } - #[cfg(test)] fn test_guardian_address2() -> Address { let secret = test_guardian_secret2(); @@ -677,8 +712,14 @@ mod tests { #[cfg(test)] fn mock_guardian_set13() -> Vec
{ vec![ - Address::from([0x58, 0x93, 0xB5, 0xA7, 0x6c, 0x3f, 0x73, 0x96, 0x45, 0x64, 0x88, 0x85, 0xbD, 0xCc, 0xC0, 0x6c, 0xd7, 0x0a, 0x3C, 0xd3]), - Address::from([0xff, 0x6C, 0xB9, 0x52, 0x58, 0x9B, 0xDE, 0x86, 0x2c, 0x25, 0xEf, 0x43, 0x92, 0x13, 0x2f, 0xb9, 0xD4, 0xA4, 0x21, 0x57]), + Address::from([ + 0x58, 0x93, 0xB5, 0xA7, 0x6c, 0x3f, 0x73, 0x96, 0x45, 0x64, 0x88, 0x85, 0xbD, 0xCc, + 0xC0, 0x6c, 0xd7, 0x0a, 0x3C, 0xd3, + ]), + Address::from([ + 0xff, 0x6C, 0xB9, 0x52, 0x58, 0x9B, 0xDE, 0x86, 0x2c, 0x25, 0xEf, 0x43, 0x92, 0x13, + 0x2f, 0xb9, 0xD4, 0xA4, 0x21, 0x57, + ]), ] } @@ -711,7 +752,11 @@ mod tests { } } - fn create_test_vaa_with_emitter(guardian_set_index: u32, signatures: Vec, emitter: Address) -> VerifiedVM { + fn create_test_vaa_with_emitter( + guardian_set_index: u32, + signatures: Vec, + emitter: Address, + ) -> VerifiedVM { let mut emitter_bytes = [0u8; 32]; emitter_bytes[12..32].copy_from_slice(emitter.as_slice()); @@ -730,7 +775,10 @@ mod tests { } } - fn create_valid_guardian_signature(guardian_index: u8, hash: &FixedBytes<32>) -> Result { + fn create_valid_guardian_signature( + guardian_index: u8, + hash: &FixedBytes<32>, + ) -> Result { let secret_bytes = match guardian_index { 0 => test_guardian_secret1(), 1 => test_guardian_secret2(), @@ -740,10 +788,13 @@ mod tests { let signing_key = SigningKey::from_bytes(&secret_bytes.into()) .map_err(|_| WormholeError::InvalidInput)?; - let hash_array: [u8; 32] = hash.as_slice().try_into() + let hash_array: [u8; 32] = hash + .as_slice() + .try_into() .map_err(|_| WormholeError::InvalidInput)?; - let (signature, recovery_id) = signing_key.sign_prehash_recoverable(&hash_array) + let (signature, recovery_id) = signing_key + .sign_prehash_recoverable(&hash_array) .map_err(|_| WormholeError::InvalidInput)?; let mut sig_bytes = [0u8; 65]; @@ -1113,7 +1164,6 @@ mod tests { let gov_contract = wormhole_contract.sender(alice).governance_contract(); let expected = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); assert_eq!(gov_contract, expected); - } #[motsu::test] @@ -1127,7 +1177,8 @@ mod tests { #[motsu::test] fn test_initialize_contract_like_shell_script(wormhole_contract: Contract, alice: Address) { let guardians = current_guardians(); - let governance_contract = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); + let governance_contract = + Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); let result = wormhole_contract.sender(alice).initialize(guardians.clone(), 4, CHAIN_ID, GOVERNANCE_CHAIN_ID, governance_contract); assert!(result.is_ok(), "Contract initialization should succeed"); @@ -1136,13 +1187,17 @@ mod tests { #[motsu::test] fn test_quorum_calculation_integration_test(wormhole_contract: Contract, alice: Address) { let quorum_result = WormholeContract::quorum(3); - assert_eq!(quorum_result, 3, "Quorum calculation should work: (3 * 2) / 3 + 1 = 3"); + assert_eq!( + quorum_result, 3, + "Quorum calculation should work: (3 * 2) / 3 + 1 = 3" + ); } #[motsu::test] fn test_guardian_set_retrieval_current_guardians(wormhole_contract: Contract, alice: Address) { let guardians = current_guardians(); - let governance_contract = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); + let governance_contract = + Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); let _ = wormhole_contract.sender(alice).initialize(guardians.clone(), 4, CHAIN_ID, GOVERNANCE_CHAIN_ID, governance_contract); @@ -1150,7 +1205,11 @@ mod tests { assert!(guardian_set_result.is_ok(), "Guardian set retrieval should work - contract is initialized"); let guardian_set_bytes = guardian_set_result.unwrap(); - assert_eq!(guardian_set_bytes.len(), 19 * 20, "Should have 19 guardian addresses (20 bytes each)"); + assert_eq!( + guardian_set_bytes.len(), + 19 * 20, + "Should have 19 guardian addresses (20 bytes each)" + ); assert_eq!(wormhole_contract.sender(alice).chain_id(), CHAIN_ID, "Chain ID should match shell script value"); @@ -1164,7 +1223,8 @@ mod tests { #[motsu::test] fn test_duplicate_verification(wormhole_contract: Contract, alice: Address) { let guardians = current_guardians_duplicate(); - let governance_contract = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); + let governance_contract = + Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); let _ = wormhole_contract.sender(alice).initialize(guardians.clone(), 4, CHAIN_ID, GOVERNANCE_CHAIN_ID, governance_contract); @@ -1179,7 +1239,8 @@ mod tests { #[motsu::test] fn switch_guardian_set(wormhole_contract: Contract, alice: Address) { let guardians = current_guardians_duplicate(); - let governance_contract = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); + let governance_contract = + Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); let _ = wormhole_contract.sender(alice).initialize(guardians.clone(), 3, CHAIN_ID, GOVERNANCE_CHAIN_ID, governance_contract); let test_vaa = create_vaa_bytes("AQAAAAQNAInUwKI1ItLfYeLaAibn9oXaouTs9BL3Aa9DKCFWrLu0KDaQQMQJlih0Qh7l7yH2o6kD/g9RCmRwZJ6q0OZE0t4AArCSH1wpX04N1U59tQmss2xXZilimAMKlogp7ErAhAo0LFkDogqB74+2By9rm3P5OUWlbC0lrFNut5CQQV38DGsAAxO+1nUTUc842P2afDSjdWcmjvJl2s8secQzuiW8zrdgPpbzhzWsiYXizLQBRKigDS8pWGD4vRk0fuR8H/ZkO/0BBOmDobl1BLNJx7+Pt+NWfuOUBipVFIXGxI9b3vxxH0BIec8hhxDN4m2Pd2I0klGEXKhv9plcR7VlzAsaC7ZE7QIABh4ff66tP7EHdVfZR4mTzv5B97agMcSB1eDeijpyl9JuBhbMupw7nExZNnZag/x2k6AUEWnQnfp8AoaCK7Av+icAB2Ouk9mPd1ybyju39Q8m7GMevt2f1nHVyWVsPRzdEcCuAbzjh5137DCLzVWuFUujTQJ7IJiznQb6cm2Ljk3WOXUACMa/JwRdpVKZf6eTD6O6tivqhdhMtbijlPBZX/kgVKk5Xuyv3h1SRTrNCwkMg5XOWegnCbXqjbUlo+F3qTjCalQBCxfp1itJskZmv+SXA47QivURKWzGa3mntNh0vcAXYi8FeChvoUYmfYpejmBlOkD1I73pmUsyrbYbetHa7qFu3eoBDZScdyrWp2dS5Y9L4b0who/PncVp5oFs/4J8ThHNQoXWXvys+nUc2aM+E+Fwazo2ODdI8XZz9YOGf/ZfE6iXFBYBDgckow8Nb2QD//C6MfP2Bz8zftqvt+D6Dko7v/Inb2OtCj342yjrxcvAMlCQ6lYoTIAMNemzNoqlfNyDMdB9yKoAEKebRtCm8QZSjLQ5uPk8aoQpmNwCpLhiHuzh2fqH55fcQrE6/KFttfw7VzeGUE7k3PF6xIMq0BPr3vkG2MedIh8BEQvpmYK4fChLY5JG26Kk6KuZ1eCkJAOQgdSjWasAvNgsSIlsb5mFjIkGwK9j20svLSl+OJ7I0olefXcZ2JywjgYAEu1jITMLHCMR1blXENulhApdhMfTef1aQ/USMqRVWNigausEzq49Hi2GtcQzHmZuhgnhBZEnjq9K8jsZwJk59iwBaFxZegAAAAAAATTNxrJiPzbWCugg6Vtg92ToHsLNO1e3fj+OJd3UOsNzAAAAAAATpFIAAVE6cNLnZT2Noq5nJ4VNRSf2KrRBNrlimFaXauHv3efDAAFm5RiKEwih25C20x8/vcqMPfJnjIES3909GSxaPMRXqAAAAAAAAAAAAAAAAFxIFHGlrpnuxd5M5WePQalLpUyHAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALFcwAAAAAAAAAAAAAAAAaFxdzQAAAAAAAAAAAAAAAK3MabLDE8LWvGN6+AdUvFHJdm5RAAMAAAAAAAAAAAAAAADf0SJhChSsEtk0iYwC2+wfcnCBFg=="); @@ -1199,6 +1260,4 @@ mod tests { let result2 = wormhole_contract.sender(alice).parse_and_verify_vm(test_vaa.clone()); assert!(result2.is_ok()); } - - } diff --git a/target_chains/stylus/contracts/wormhole/tests/integration_test.rs b/target_chains/stylus/contracts/wormhole/tests/integration_test.rs index e03db007c6..d058f3783d 100644 --- a/target_chains/stylus/contracts/wormhole/tests/integration_test.rs +++ b/target_chains/stylus/contracts/wormhole/tests/integration_test.rs @@ -5,10 +5,12 @@ use core::str::FromStr; use stylus_sdk::alloy_primitives::{Address, FixedBytes, U256}; use motsu::prelude::Contract; use k256::ecdsa::SigningKey; +use motsu::prelude::DefaultStorage; use stylus_sdk::alloy_primitives::keccak256; +use wormhole_contract::*; -use base64::engine::general_purpose; use base64::Engine; +use base64::engine::general_purpose; const CHAIN_ID: u16 = 60051; const GOVERNANCE_CHAIN_ID: u16 = 1; @@ -19,28 +21,24 @@ fn test_wormhole_vaa() -> Vec { } fn create_vaa_bytes(input_string: &str) -> Vec { - let vaa_bytes = general_purpose::STANDARD - .decode(input_string) - .unwrap(); + let vaa_bytes = general_purpose::STANDARD.decode(input_string).unwrap(); let vaa: Vec = vaa_bytes; vaa } fn test_guardian_secret1() -> [u8; 32] { [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, + 0x1f, 0x20, ] } fn test_guardian_secret2() -> [u8; 32] { [ - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, ] } @@ -104,8 +102,6 @@ fn test_guardian_address1() -> Address { Address::from(address_bytes) } - - fn test_guardian_address2() -> Address { let secret = test_guardian_secret2(); let signing_key = SigningKey::from_bytes(&secret.into()).expect("key"); @@ -145,12 +141,10 @@ fn deploy_with_mainnet_guardians(wormhole_contract: &Contract, wormhole_contract.sender(*alice).initialize(guardians, 0, CHAIN_ID, GOVERNANCE_CHAIN_ID, governance_contract).unwrap(); } - fn guardian_set0() -> Vec
{ vec![Address::from_str("0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5").unwrap()] } - fn guardian_set4() -> Vec
{ vec![ Address::from_str("0x5893B5A76c3f739645648885bDCcC06cd70a3Cd3").unwrap(), @@ -159,15 +153,19 @@ fn guardian_set4() -> Vec
{ ] } - fn mock_guardian_set13() -> Vec
{ vec![ - Address::from([0x58, 0x93, 0xB5, 0xA7, 0x6c, 0x3f, 0x73, 0x96, 0x45, 0x64, 0x88, 0x85, 0xbD, 0xCc, 0xC0, 0x6c, 0xd7, 0x0a, 0x3C, 0xd3]), - Address::from([0xff, 0x6C, 0xB9, 0x52, 0x58, 0x9B, 0xDE, 0x86, 0x2c, 0x25, 0xEf, 0x43, 0x92, 0x13, 0x2f, 0xb9, 0xD4, 0xA4, 0x21, 0x57]), + Address::from([ + 0x58, 0x93, 0xB5, 0xA7, 0x6c, 0x3f, 0x73, 0x96, 0x45, 0x64, 0x88, 0x85, 0xbD, 0xCc, + 0xC0, 0x6c, 0xd7, 0x0a, 0x3C, 0xd3, + ]), + Address::from([ + 0xff, 0x6C, 0xB9, 0x52, 0x58, 0x9B, 0xDE, 0x86, 0x2c, 0x25, 0xEf, 0x43, 0x92, 0x13, + 0x2f, 0xb9, 0xD4, 0xA4, 0x21, 0x57, + ]), ] } - fn corrupted_vaa(mut real_data: Vec, pos: usize, random1: u8, random2: u8) -> Vec { if real_data.len() < 2 { return real_data; @@ -196,7 +194,11 @@ fn create_test_vaa(guardian_set_index: u32, signatures: Vec) } } -fn create_test_vaa_with_emitter(guardian_set_index: u32, signatures: Vec, emitter: Address) -> VerifiedVM { +fn create_test_vaa_with_emitter( + guardian_set_index: u32, + signatures: Vec, + emitter: Address, +) -> VerifiedVM { let mut emitter_bytes = [0u8; 32]; emitter_bytes[12..32].copy_from_slice(emitter.as_slice()); @@ -215,20 +217,26 @@ fn create_test_vaa_with_emitter(guardian_set_index: u32, signatures: Vec) -> Result { +fn create_valid_guardian_signature( + guardian_index: u8, + hash: &FixedBytes<32>, +) -> Result { let secret_bytes = match guardian_index { 0 => test_guardian_secret1(), 1 => test_guardian_secret2(), _ => test_guardian_secret1(), }; - let signing_key = SigningKey::from_bytes(&secret_bytes.into()) - .map_err(|_| WormholeError::InvalidInput)?; + let signing_key = + SigningKey::from_bytes(&secret_bytes.into()).map_err(|_| WormholeError::InvalidInput)?; - let hash_array: [u8; 32] = hash.as_slice().try_into() + let hash_array: [u8; 32] = hash + .as_slice() + .try_into() .map_err(|_| WormholeError::InvalidInput)?; - let (signature, recovery_id) = signing_key.sign_prehash_recoverable(&hash_array) + let (signature, recovery_id) = signing_key + .sign_prehash_recoverable(&hash_array) .map_err(|_| WormholeError::InvalidInput)?; let mut sig_bytes = [0u8; 65]; @@ -241,7 +249,6 @@ fn create_valid_guardian_signature(guardian_index: u8, hash: &FixedBytes<32>) -> }) } - fn create_guardian_signature(guardian_index: u8) -> GuardianSignature { GuardianSignature { guardian_index, @@ -353,7 +360,6 @@ fn test_chain_id_governance_values(wormhole_contract: Contract let gov_contract = wormhole_contract.sender(alice).governance_contract(); let expected = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); assert_eq!(gov_contract, expected); - } #[motsu::test] @@ -384,7 +390,11 @@ fn test_guardian_set_retrieval_current_guardians(wormhole_contract: Contract, al let result = wormhole_contract.sender(alice).parse_and_verify_vm(test_vaa); println!("result: {:?}", result); assert!(result.is_err()); -} +} \ No newline at end of file