diff --git a/contracts/wormhole-old/Cargo.toml b/contracts/wormhole-old/Cargo.toml deleted file mode 100644 index 3db59318c..000000000 --- a/contracts/wormhole-old/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "wormhole" -version = "1.0.0" -authors = ["Artur Troian u8; - fn get_u16(&self, index: usize) -> u16; - fn get_u32(&self, index: usize) -> u32; - fn get_u64(&self, index: usize) -> u64; - fn get_u128(&self, index: usize) -> u128; - fn get_u256(&self, index: usize) -> (u128, u128); - fn get_bytes32(&self, index: usize) -> &[u8]; - fn get_address(&self, index: usize) -> CanonicalAddr; -} - -impl ByteUtils for &[u8] { - fn get_u8(&self, index: usize) -> u8 { - self[index] - } - - fn get_u16(&self, index: usize) -> u16 { - let mut bytes = [0u8; 2]; - bytes.copy_from_slice(&self[index..index + 2]); - u16::from_be_bytes(bytes) - } - - fn get_u32(&self, index: usize) -> u32 { - let mut bytes = [0u8; 4]; - bytes.copy_from_slice(&self[index..index + 4]); - u32::from_be_bytes(bytes) - } - - fn get_u64(&self, index: usize) -> u64 { - let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&self[index..index + 8]); - u64::from_be_bytes(bytes) - } - - fn get_u128(&self, index: usize) -> u128 { - let mut bytes = [0u8; 16]; - bytes.copy_from_slice(&self[index..index + 16]); - u128::from_be_bytes(bytes) - } - - fn get_u256(&self, index: usize) -> (u128, u128) { - (self.get_u128(index), self.get_u128(index + 16)) - } - - fn get_bytes32(&self, index: usize) -> &[u8] { - &self[index..index + 32] - } - - fn get_address(&self, index: usize) -> CanonicalAddr { - CanonicalAddr::from(&self[index + 12..index + 32]) - } -} - -pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec { - let mut result = vec![0u8; 32]; - let addr_bytes = addr.as_slice(); - let start = 32 - addr_bytes.len(); - result[start..].copy_from_slice(addr_bytes); - result -} diff --git a/contracts/wormhole-old/src/contract.rs b/contracts/wormhole-old/src/contract.rs deleted file mode 100644 index 5222453ce..000000000 --- a/contracts/wormhole-old/src/contract.rs +++ /dev/null @@ -1,329 +0,0 @@ -use std::ops::Deref; - -use cosmwasm_std::{ - entry_point, to_json_binary, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, - MessageInfo, QuerierWrapper, Response, StdError, StdResult, Storage, Uint256, WasmMsg, -}; - -use crate::{ - byte_utils::{extend_address_to_32, ByteUtils}, - error::ContractError, - msg::{ - ExecuteMsg, GetAddressHexResponse, GetStateResponse, GuardianSetInfoResponse, - InstantiateMsg, MigrateMsg, QueryMsg, - }, - querier::{AkashQuerier, AkashQuery}, - state::{ - ConfigInfo, ContractUpgrade, GovernancePacket, GuardianAddress, GuardianSetInfo, - ParsedVAA, SetFee, CONFIG, SEQUENCES, VAA_ARCHIVE, - }, -}; - -use k256::ecdsa::{RecoveryId, Signature, VerifyingKey}; -use sha3::{Digest, Keccak256}; - -// Lock assets fee amount -const FEE_AMOUNT: u128 = 0; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let state = ConfigInfo { - gov_chain: msg.gov_chain, - gov_address: msg.gov_address.to_vec(), - fee: Coin::new(Uint256::from(FEE_AMOUNT), &msg.fee_denom), - chain_id: msg.chain_id, - fee_denom: msg.fee_denom.clone(), - }; - CONFIG.save(deps.storage, &state)?; - - Ok(Response::new() - .add_attribute("action", "instantiate")) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - #[cfg(feature = "full")] - ExecuteMsg::PostMessage { message, nonce } => { - handle_post_message(deps, env, info, message.as_slice(), nonce) - } - ExecuteMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, info, vaa.as_slice()), - #[cfg(not(feature = "full"))] - _ => Err(StdError::msg("Invalid during shutdown mode")), - } -} - -fn handle_submit_vaa( - deps: DepsMut, - env: Env, - _info: MessageInfo, - data: &[u8], -) -> StdResult { - let state = CONFIG.load(deps.storage)?; - - // Always use oracle-based guardian set from x/oracle params - let querier: QuerierWrapper = QuerierWrapper::new(deps.querier.deref()); - let vaa = parse_and_verify_vaa( - deps.storage, - &querier, - data, - env.block.time.seconds(), - )?; - - VAA_ARCHIVE.save(deps.storage, vaa.hash.as_slice(), &true)?; - - if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address { - return handle_governance_payload(deps, env, &vaa.payload); - } - - ContractError::InvalidVAAAction.std_err() -} - -fn handle_governance_payload(deps: DepsMut, env: Env, data: &[u8]) -> StdResult { - let gov_packet = GovernancePacket::deserialize(data)?; - let state = CONFIG.load(deps.storage)?; - - let module = String::from_utf8(gov_packet.module) - .map_err(|_| StdError::msg("invalid governance module encoding"))?; - let module: String = module.chars().filter(|c| c != &'\0').collect(); - - if module != "Core" { - return Err(StdError::msg("this is not a valid module")); - } - - if gov_packet.chain != 0 && gov_packet.chain != state.chain_id { - return Err(StdError::msg( - "the governance VAA is for another chain", - )); - } - - match gov_packet.action { - 1u8 => vaa_update_contract(deps, env, &gov_packet.payload), - // Guardian set updates (action 2) are handled via Akash governance, not Wormhole governance - #[cfg(feature = "full")] - 3u8 => handle_set_fee(deps, env, &gov_packet.payload), - _ => ContractError::InvalidVAAAction.std_err(), - } -} - -/// Parse and verify VAA using guardian set from x/oracle params. -fn parse_and_verify_vaa( - storage: &dyn Storage, - querier: &QuerierWrapper, - data: &[u8], - _block_time: u64, -) -> StdResult { - let vaa = ParsedVAA::deserialize(data)?; - - if vaa.version != 1 { - return ContractError::InvalidVersion.std_err(); - } - - if VAA_ARCHIVE.may_load(storage, vaa.hash.as_slice())?.unwrap_or(false) { - return ContractError::VaaAlreadyExecuted.std_err(); - } - - // Get guardian set from x/oracle params (only source) - let guardian_set = querier.query_guardian_set() - .map_err(|_| StdError::msg("failed to query guardian set from oracle"))? - .to_guardian_set_info(); - - if guardian_set.addresses.is_empty() { - return Err(StdError::msg("no guardian addresses configured in oracle params")); - } - - // Oracle-provided guardian sets don't expire (managed by Akash governance) - verify_vaa_signatures(&vaa, data, &guardian_set)?; - - Ok(vaa) -} - -/// Verify VAA signatures against the provided guardian set. -/// Extracted to share logic between stored and oracle-based verification. -fn verify_vaa_signatures( - vaa: &ParsedVAA, - data: &[u8], - guardian_set: &GuardianSetInfo, -) -> StdResult<()> { - if (vaa.len_signers as usize) < guardian_set.quorum() { - return ContractError::NoQuorum.std_err(); - } - - // Verify guardian signatures - let mut last_index: i32 = -1; - let mut pos = ParsedVAA::HEADER_LEN; - let data_ref: &[u8] = data; - - for _ in 0..vaa.len_signers { - if pos + ParsedVAA::SIGNATURE_LEN > data.len() { - return ContractError::InvalidVAA.std_err(); - } - - let index = data_ref.get_u8(pos) as i32; - if index <= last_index { - return ContractError::WrongGuardianIndexOrder.std_err(); - } - last_index = index; - - let sig_bytes = &data[pos + ParsedVAA::SIG_DATA_POS - ..pos + ParsedVAA::SIG_DATA_POS + ParsedVAA::SIG_DATA_LEN]; - let recovery_id = data_ref.get_u8(pos + ParsedVAA::SIG_RECOVERY_POS); - - let signature = Signature::try_from(sig_bytes) - .map_err(|_| StdError::msg("cannot decode signature"))?; - - let recovery_id = RecoveryId::try_from(recovery_id) - .map_err(|_| StdError::msg("cannot decode recovery id"))?; - - let verify_key = VerifyingKey::recover_from_prehash( - vaa.hash.as_slice(), - &signature, - recovery_id, - ) - .map_err(|_| StdError::msg("cannot recover key"))?; - - let index = index as usize; - if index >= guardian_set.addresses.len() { - return ContractError::TooManySignatures.std_err(); - } - - if !keys_equal(&verify_key, &guardian_set.addresses[index]) { - return ContractError::GuardianSignatureError.std_err(); - } - - pos += ParsedVAA::SIGNATURE_LEN; - } - - Ok(()) -} - -fn vaa_update_contract(_deps: DepsMut, env: Env, data: &[u8]) -> StdResult { - let ContractUpgrade { new_contract } = ContractUpgrade::deserialize(data)?; - - Ok(Response::new() - .add_message(CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: env.contract.address.to_string(), - new_code_id: new_contract, - msg: to_json_binary(&MigrateMsg {})?, - })) - .add_attribute("action", "contract_upgrade")) -} - -#[cfg(feature = "full")] -pub fn handle_set_fee(deps: DepsMut, _env: Env, data: &[u8]) -> StdResult { - let mut state = CONFIG.load(deps.storage)?; - let set_fee_msg = SetFee::deserialize(data, state.fee_denom.clone())?; - - state.fee = set_fee_msg.fee; - CONFIG.save(deps.storage, &state)?; - - Ok(Response::new() - .add_attribute("action", "fee_change") - .add_attribute("new_fee.amount", state.fee.amount.to_string()) - .add_attribute("new_fee.denom", state.fee.denom)) -} - -#[cfg(feature = "full")] -fn handle_post_message( - deps: DepsMut, - env: Env, - info: MessageInfo, - message: &[u8], - nonce: u32, -) -> StdResult { - let state = CONFIG.load(deps.storage)?; - let fee = &state.fee; - - // Check fee - compare Uint256 values directly - if !fee.amount.is_zero() { - let sent = info.funds.iter() - .find(|c| c.denom == fee.denom) - .map(|c| c.amount) - .unwrap_or(Uint256::zero()); - if sent < fee.amount { - return ContractError::FeeTooLow.std_err(); - } - } - - let emitter = extend_address_to_32(&deps.api.addr_canonicalize(info.sender.as_str())?); - let sequence = SEQUENCES.may_load(deps.storage, emitter.as_slice())?.unwrap_or(0); - SEQUENCES.save(deps.storage, emitter.as_slice(), &(sequence + 1))?; - - Ok(Response::new() - .add_attribute("message.message", hex::encode(message)) - .add_attribute("message.sender", hex::encode(&emitter)) - .add_attribute("message.chain_id", state.chain_id.to_string()) - .add_attribute("message.nonce", nonce.to_string()) - .add_attribute("message.sequence", sequence.to_string()) - .add_attribute("message.block_time", env.block.time.seconds().to_string())) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GuardianSetInfo {} => to_json_binary(&query_guardian_set_info(deps)?), - QueryMsg::VerifyVAA { vaa, block_time } => { - to_json_binary(&query_parse_and_verify_vaa(deps, vaa.as_slice(), block_time)?) - } - QueryMsg::GetState {} => to_json_binary(&query_state(deps)?), - QueryMsg::QueryAddressHex { address } => to_json_binary(&query_address_hex(deps, &address)?), - } -} - -pub fn query_guardian_set_info(deps: Deps) -> StdResult { - // Always get guardian set from x/oracle params - let querier: QuerierWrapper = QuerierWrapper::new(deps.querier.deref()); - let response = querier.query_guardian_set() - .map_err(|_| StdError::msg("failed to query guardian set"))?; - - let guardian_set = response.to_guardian_set_info(); - Ok(GuardianSetInfoResponse { - // Index 0 indicates oracle-sourced guardian set - guardian_set_index: 0, - addresses: guardian_set.addresses, - }) -} - -pub fn query_parse_and_verify_vaa(deps: Deps, data: &[u8], block_time: u64) -> StdResult { - // Always use oracle-based guardian set - let querier: QuerierWrapper = QuerierWrapper::new(deps.querier.deref()); - parse_and_verify_vaa(deps.storage, &querier, data, block_time) -} - -pub fn query_address_hex(deps: Deps, address: &str) -> StdResult { - Ok(GetAddressHexResponse { - hex: hex::encode(extend_address_to_32(&deps.api.addr_canonicalize(address)?)), - }) -} - -pub fn query_state(deps: Deps) -> StdResult { - let state = CONFIG.load(deps.storage)?; - Ok(GetStateResponse { fee: state.fee }) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { - Ok(Response::default()) -} - -#[allow(unused_imports)] -fn keys_equal(a: &VerifyingKey, b: &GuardianAddress) -> bool { - use k256::elliptic_curve::sec1::ToEncodedPoint; - - let mut hasher = Keccak256::new(); - let point = a.to_encoded_point(false); - hasher.update(&point.as_bytes()[1..]); - let a_hash = &hasher.finalize()[12..]; - - let b_bytes = b.bytes.as_slice(); - if a_hash.len() != b_bytes.len() { - return false; - } - - a_hash.iter().zip(b_bytes.iter()).all(|(ai, bi)| ai == bi) -} diff --git a/contracts/wormhole-old/src/error.rs b/contracts/wormhole-old/src/error.rs deleted file mode 100644 index 6424e4ad8..000000000 --- a/contracts/wormhole-old/src/error.rs +++ /dev/null @@ -1,59 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Invalid VAA version")] - InvalidVersion, - - #[error("Invalid VAA")] - InvalidVAA, - - #[error("VAA has already been executed")] - VaaAlreadyExecuted, - - #[error("Invalid guardian set index")] - InvalidGuardianSetIndex, - - #[error("Guardian set has expired")] - GuardianSetExpired, - - #[error("No quorum")] - NoQuorum, - - #[error("Wrong guardian index order")] - WrongGuardianIndexOrder, - - #[error("Cannot decode signature")] - CannotDecodeSignature, - - #[error("Cannot recover key")] - CannotRecoverKey, - - #[error("Too many signatures")] - TooManySignatures, - - #[error("Guardian signature verification failed")] - GuardianSignatureError, - - #[error("Invalid VAA action")] - InvalidVAAAction, - - #[error("Guardian set index increase error")] - GuardianSetIndexIncreaseError, - - #[error("Fee too low")] - FeeTooLow, -} - -impl ContractError { - pub fn std_err(self) -> Result { - Err(StdError::msg(self.to_string())) - } -} diff --git a/contracts/wormhole-old/src/lib.rs b/contracts/wormhole-old/src/lib.rs deleted file mode 100644 index e32344220..000000000 --- a/contracts/wormhole-old/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod byte_utils; -pub mod contract; -pub mod error; -pub mod msg; -pub mod querier; -pub mod state; diff --git a/contracts/wormhole-old/src/msg.rs b/contracts/wormhole-old/src/msg.rs deleted file mode 100644 index 09e1ed6e3..000000000 --- a/contracts/wormhole-old/src/msg.rs +++ /dev/null @@ -1,65 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Binary, Coin}; - -#[allow(unused_imports)] -use crate::state::{GuardianAddress, GuardianSetInfo, ParsedVAA}; - -#[cw_serde] -pub struct InstantiateMsg { - /// Governance chain ID (typically Solana = 1) - pub gov_chain: u16, - /// Governance contract address - pub gov_address: Binary, - /// Chain ID for this deployment - pub chain_id: u16, - /// Fee denomination - pub fee_denom: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Submit a VAA for verification and execution - SubmitVAA { vaa: Binary }, - /// Post a message (only in full mode) - #[cfg(feature = "full")] - PostMessage { message: Binary, nonce: u32 }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Get current guardian set info - #[returns(GuardianSetInfoResponse)] - GuardianSetInfo {}, - - /// Verify a VAA without executing it - #[returns(ParsedVAA)] - VerifyVAA { vaa: Binary, block_time: u64 }, - - /// Get contract state - #[returns(GetStateResponse)] - GetState {}, - - /// Get address in hex format - #[returns(GetAddressHexResponse)] - QueryAddressHex { address: String }, -} - -#[cw_serde] -pub struct MigrateMsg {} - -#[cw_serde] -pub struct GuardianSetInfoResponse { - pub guardian_set_index: u32, - pub addresses: Vec, -} - -#[cw_serde] -pub struct GetStateResponse { - pub fee: Coin, -} - -#[cw_serde] -pub struct GetAddressHexResponse { - pub hex: String, -} diff --git a/contracts/wormhole-old/src/querier.rs b/contracts/wormhole-old/src/querier.rs deleted file mode 100644 index 2cc760569..000000000 --- a/contracts/wormhole-old/src/querier.rs +++ /dev/null @@ -1,66 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Binary, CustomQuery, QuerierWrapper, StdResult}; - -use crate::state::{GuardianAddress, GuardianSetInfo}; - -/// Custom query type for Akash chain queries -#[cw_serde] -pub enum AkashQuery { - /// Query the Wormhole guardian set from x/oracle params - GuardianSet {}, -} - -impl CustomQuery for AkashQuery {} - -/// Response for guardian set query from x/oracle params. -/// Matches the Go type in x/wasm/bindings/akash_query.go -#[cw_serde] -pub struct GuardianSetResponse { - /// List of guardian addresses (20 bytes each, base64 encoded) - pub addresses: Vec, - /// When this guardian set expires (0 = never) - pub expiration_time: u64, -} - -/// Guardian address in the response (base64 encoded Binary) -#[cw_serde] -pub struct GuardianAddressResponse { - /// 20-byte guardian address, base64 encoded - pub bytes: Binary, -} - -impl GuardianSetResponse { - /// Convert to GuardianSetInfo for use in VAA verification - pub fn to_guardian_set_info(&self) -> GuardianSetInfo { - GuardianSetInfo { - addresses: self - .addresses - .iter() - .map(|addr| GuardianAddress { - bytes: addr.bytes.clone(), - }) - .collect(), - expiration_time: self.expiration_time, - } - } -} - -/// Extension trait for querying Akash-specific data -pub trait AkashQuerier { - fn query_guardian_set(&self) -> StdResult; -} - -impl<'a> AkashQuerier for QuerierWrapper<'a, AkashQuery> { - fn query_guardian_set(&self) -> StdResult { - self.query(&AkashQuery::GuardianSet {}.into()) - } -} - -/// Query the guardian set from x/oracle params. -/// This allows the Wormhole contract to use guardian keys managed by Akash governance. -pub fn query_guardian_set_from_oracle( - querier: &QuerierWrapper, -) -> StdResult { - let response = querier.query_guardian_set()?; - Ok(response.to_guardian_set_info()) -} diff --git a/contracts/wormhole-old/src/state.rs b/contracts/wormhole-old/src/state.rs deleted file mode 100644 index 5172b2af6..000000000 --- a/contracts/wormhole-old/src/state.rs +++ /dev/null @@ -1,350 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Binary, Coin, StdError, StdResult, Uint256}; -use cw_storage_plus::{Item, Map}; - -use crate::byte_utils::ByteUtils; -use crate::error::ContractError; - -/// Contract configuration -#[cw_serde] -pub struct ConfigInfo { - /// Governance chain (typically Solana = 1) - pub gov_chain: u16, - /// Governance contract address - pub gov_address: Vec, - /// Message sending fee - pub fee: Coin, - /// Chain ID for this deployment - pub chain_id: u16, - /// Fee denomination - pub fee_denom: String, -} - -/// Parsed VAA (Verified Action Approval) -#[cw_serde] -pub struct ParsedVAA { - pub version: u8, - pub guardian_set_index: u32, - pub timestamp: u32, - pub nonce: u32, - pub len_signers: u8, - pub emitter_chain: u16, - pub emitter_address: Vec, - pub sequence: u64, - pub consistency_level: u8, - pub payload: Vec, - pub hash: Vec, -} - -impl ParsedVAA { - pub const HEADER_LEN: usize = 6; - pub const SIGNATURE_LEN: usize = 66; - - pub const GUARDIAN_SET_INDEX_POS: usize = 1; - pub const LEN_SIGNER_POS: usize = 5; - - pub const VAA_NONCE_POS: usize = 4; - pub const VAA_EMITTER_CHAIN_POS: usize = 8; - pub const VAA_EMITTER_ADDRESS_POS: usize = 10; - pub const VAA_SEQUENCE_POS: usize = 42; - pub const VAA_CONSISTENCY_LEVEL_POS: usize = 50; - pub const VAA_PAYLOAD_POS: usize = 51; - - pub const SIG_DATA_POS: usize = 1; - pub const SIG_DATA_LEN: usize = 64; - pub const SIG_RECOVERY_POS: usize = Self::SIG_DATA_POS + Self::SIG_DATA_LEN; - - pub fn deserialize(data: &[u8]) -> StdResult { - use sha3::{Digest, Keccak256}; - - let data_ref: &[u8] = data; - let version = data_ref.get_u8(0); - let guardian_set_index = data_ref.get_u32(Self::GUARDIAN_SET_INDEX_POS); - let len_signers = data_ref.get_u8(Self::LEN_SIGNER_POS) as usize; - let body_offset = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers; - - if body_offset >= data.len() { - return ContractError::InvalidVAA.std_err(); - } - - let body = &data[body_offset..]; - let mut hasher = Keccak256::new(); - hasher.update(body); - let hash = hasher.finalize().to_vec(); - - let mut hasher = Keccak256::new(); - hasher.update(&hash); - let hash = hasher.finalize().to_vec(); - - if body_offset + Self::VAA_PAYLOAD_POS > data.len() { - return ContractError::InvalidVAA.std_err(); - } - - let timestamp = data_ref.get_u32(body_offset); - let nonce = data_ref.get_u32(body_offset + Self::VAA_NONCE_POS); - let emitter_chain = data_ref.get_u16(body_offset + Self::VAA_EMITTER_CHAIN_POS); - let emitter_address = data_ref - .get_bytes32(body_offset + Self::VAA_EMITTER_ADDRESS_POS) - .to_vec(); - let sequence = data_ref.get_u64(body_offset + Self::VAA_SEQUENCE_POS); - let consistency_level = data_ref.get_u8(body_offset + Self::VAA_CONSISTENCY_LEVEL_POS); - let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec(); - - Ok(ParsedVAA { - version, - guardian_set_index, - timestamp, - nonce, - len_signers: len_signers as u8, - emitter_chain, - emitter_address, - sequence, - consistency_level, - payload, - hash, - }) - } -} - -/// Guardian address (20 bytes, Ethereum-style) -#[cw_serde] -pub struct GuardianAddress { - pub bytes: Binary, -} - -#[cfg(test)] -impl GuardianAddress { - pub fn from(string: &str) -> GuardianAddress { - GuardianAddress { - bytes: hex::decode(string).expect("Decoding failed").into(), - } - } -} - -/// Guardian set information -#[cw_serde] -pub struct GuardianSetInfo { - pub addresses: Vec, - pub expiration_time: u64, -} - -impl GuardianSetInfo { - pub fn quorum(&self) -> usize { - if self.addresses.is_empty() { - return 0; - } - ((self.addresses.len() * 10 / 3) * 2) / 10 + 1 - } -} - -/// Governance packet structure -pub struct GovernancePacket { - pub module: Vec, - pub action: u8, - pub chain: u16, - pub payload: Vec, -} - -impl GovernancePacket { - pub fn deserialize(data: &[u8]) -> StdResult { - if data.len() < 35 { - return ContractError::InvalidVAA.std_err(); - } - let data_ref: &[u8] = data; - let module = data_ref.get_bytes32(0).to_vec(); - let action = data_ref.get_u8(32); - let chain = data_ref.get_u16(33); - let payload = data[35..].to_vec(); - - Ok(GovernancePacket { - module, - action, - chain, - payload, - }) - } -} - -/// Contract upgrade governance action -pub struct ContractUpgrade { - pub new_contract: u64, -} - -impl ContractUpgrade { - pub fn deserialize(data: &[u8]) -> StdResult { - if data.len() < 32 { - return ContractError::InvalidVAA.std_err(); - } - let data_ref: &[u8] = data; - let new_contract = data_ref.get_u64(24); - Ok(ContractUpgrade { new_contract }) - } -} - -/// Guardian set upgrade governance action -pub struct GuardianSetUpgrade { - pub new_guardian_set_index: u32, - pub new_guardian_set: GuardianSetInfo, -} - -impl GuardianSetUpgrade { - pub fn deserialize(data: &[u8]) -> StdResult { - const ADDRESS_LEN: usize = 20; - - if data.len() < 5 { - return ContractError::InvalidVAA.std_err(); - } - let data_ref: &[u8] = data; - let new_guardian_set_index = data_ref.get_u32(0); - let n_guardians = data_ref.get_u8(4); - - let mut addresses = vec![]; - for i in 0..n_guardians { - let pos = 5 + (i as usize) * ADDRESS_LEN; - if pos + ADDRESS_LEN > data.len() { - return ContractError::InvalidVAA.std_err(); - } - addresses.push(GuardianAddress { - bytes: data[pos..pos + ADDRESS_LEN].to_vec().into(), - }); - } - - let new_guardian_set = GuardianSetInfo { - addresses, - expiration_time: 0, - }; - - Ok(GuardianSetUpgrade { - new_guardian_set_index, - new_guardian_set, - }) - } -} - -/// Set fee governance action -pub struct SetFee { - pub fee: Coin, -} - -impl SetFee { - pub fn deserialize(data: &[u8], fee_denom: String) -> StdResult { - if data.len() < 32 { - return ContractError::InvalidVAA.std_err(); - } - let data_ref: &[u8] = data; - let (upper, amount) = data_ref.get_u256(0); - if upper != 0 { - return Err(StdError::msg("fee overflow: upper 128 bits are non-zero")); - } - let fee = Coin { - denom: fee_denom, - amount: Uint256::from(amount), - }; - Ok(SetFee { fee }) - } -} - -// Storage items -pub const CONFIG: Item = Item::new("config"); -pub const SEQUENCES: Map<&[u8], u64> = Map::new("sequences"); -pub const VAA_ARCHIVE: Map<&[u8], bool> = Map::new("vaa_archive"); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_governance_packet_short_input() { - // 10 bytes is far below the 35-byte minimum - let data = vec![0u8; 10]; - let result = GovernancePacket::deserialize(&data); - assert!(result.is_err(), "should reject input shorter than 35 bytes"); - } - - #[test] - fn test_governance_packet_exact_minimum() { - // Exactly 35 bytes — minimum valid governance packet (empty payload) - let data = vec![0u8; 35]; - let result = GovernancePacket::deserialize(&data); - assert!(result.is_ok(), "35 bytes should be accepted"); - let packet = result.unwrap(); - assert_eq!(packet.module.len(), 32); - assert_eq!(packet.action, 0); - assert_eq!(packet.chain, 0); - assert!(packet.payload.is_empty()); - } - - #[test] - fn test_contract_upgrade_short_input() { - // 20 bytes is below the 32-byte minimum - let data = vec![0u8; 20]; - let result = ContractUpgrade::deserialize(&data); - assert!(result.is_err(), "should reject input shorter than 32 bytes"); - } - - #[test] - fn test_contract_upgrade_valid() { - let mut data = vec![0u8; 32]; - // Place value 42 at offset 24 (big-endian u64) - data[31] = 42; - let result = ContractUpgrade::deserialize(&data); - assert!(result.is_ok()); - assert_eq!(result.unwrap().new_contract, 42); - } - - #[test] - fn test_guardian_set_upgrade_short_input() { - // 3 bytes is below the 5-byte minimum - let data = vec![0u8; 3]; - let result = GuardianSetUpgrade::deserialize(&data); - assert!(result.is_err(), "should reject input shorter than 5 bytes"); - } - - #[test] - fn test_guardian_set_upgrade_zero_guardians() { - // 5 bytes: 4-byte index + 1-byte count (0 guardians) - let data = vec![0u8; 5]; - let result = GuardianSetUpgrade::deserialize(&data); - assert!(result.is_ok()); - let upgrade = result.unwrap(); - assert_eq!(upgrade.new_guardian_set_index, 0); - assert!(upgrade.new_guardian_set.addresses.is_empty()); - } - - #[test] - fn test_set_fee_short_input() { - // 16 bytes is below the 32-byte minimum - let data = vec![0u8; 16]; - let result = SetFee::deserialize(&data, "uakt".to_string()); - assert!(result.is_err(), "should reject input shorter than 32 bytes"); - } - - #[test] - fn test_set_fee_u256_overflow() { - // Upper 128 bits are non-zero — must be rejected - let mut data = vec![0u8; 32]; - data[0] = 1; // sets upper 128-bit portion to non-zero - let result = SetFee::deserialize(&data, "uakt".to_string()); - assert!(result.is_err(), "should reject fee with non-zero upper 128 bits"); - let err_msg = format!("{}", result.err().unwrap()); - assert!( - err_msg.contains("fee overflow"), - "error should mention fee overflow, got: {}", - err_msg - ); - } - - #[test] - fn test_set_fee_valid() { - // Valid 32-byte input with upper 128 bits = 0, lower = 1000 - let mut data = vec![0u8; 32]; - // Place 1000 (0x3E8) in the lower 128 bits at offset 16..32 - data[30] = 0x03; - data[31] = 0xE8; - let result = SetFee::deserialize(&data, "uakt".to_string()); - assert!(result.is_ok()); - let fee = result.unwrap().fee; - assert_eq!(fee.denom, "uakt"); - assert_eq!(fee.amount, Uint256::from(1000u128)); - } -}