diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index 68ba7421c5..1799e32f53 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -98,15 +98,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -2685,7 +2676,6 @@ dependencies = [ name = "wormhole-cosmwasm" version = "0.1.0" dependencies = [ - "bincode", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", @@ -2700,6 +2690,7 @@ dependencies = [ "sha3 0.9.1", "thiserror", "tiny-keccak", + "wormchain-ibc-receiver", "wormhole-bindings", "wormhole-vaas-serde", ] diff --git a/cosmwasm/contracts/wormhole/Cargo.toml b/cosmwasm/contracts/wormhole/Cargo.toml index ef18cca41b..e1ad59868e 100644 --- a/cosmwasm/contracts/wormhole/Cargo.toml +++ b/cosmwasm/contracts/wormhole/Cargo.toml @@ -37,4 +37,4 @@ wormhole-sdk.workspace = true wormhole-bindings = { version = "0.1", features = ["fake"] } tiny-keccak = { version = "2.0", features = ["keccak"] } serde-json-wasm = "0.4" -bincode = "1.3" \ No newline at end of file +wormchain-ibc-receiver = { path = "../wormchain-ibc-receiver" } diff --git a/cosmwasm/contracts/wormhole/src/testing/integration.rs b/cosmwasm/contracts/wormhole/src/testing/integration.rs index 2abf1a0598..489d14d537 100644 --- a/cosmwasm/contracts/wormhole/src/testing/integration.rs +++ b/cosmwasm/contracts/wormhole/src/testing/integration.rs @@ -1,33 +1,32 @@ -use crate::msg::{GetAddressHexResponse, GetStateResponse, GuardianSetInfoResponse}; +use std::ops::Deref; +use std::u64; + +use crate::msg::{ExecuteMsg, GetStateResponse, GuardianSetInfoResponse}; use crate::testing::utils::{ - create_gov_vaa_body, create_transfer_vaa_body, instantiate_with_faker_guardians, sign_vaa_body, - WormholeApp, + create_transfer_vaa_body, instantiate_with_guardians, sign_vaa_body_version_2, + IntoGuardianAddress, WormholeApp, }; use crate::{ contract::instantiate, msg::QueryMsg, state::{ConfigInfo, GuardianAddress, ParsedVAA, CONFIG_KEY}, - testing::utils::instantiate_with_guardians, }; -use cosmwasm_std::Uint128; use cosmwasm_std::{ from_slice, testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - to_binary, Coin, OwnedDeps, Response, StdResult, Storage, + Coin, OwnedDeps, Response, StdResult, Storage, }; +use cosmwasm_std::{Deps, DepsMut, Empty, QuerierWrapper, StdError, Uint128, Uint256}; use cosmwasm_storage::to_length_prefixed; -use cw_multi_test::Executor; +use cw_multi_test::{ContractWrapper, Executor}; use k256::ecdsa::SigningKey; -use serde::{de::IntoDeserializer, Deserialize}; use serde_wormhole::RawMessage; -use std::convert::TryInto; -use wormhole_bindings::fake::WormholeKeeper; +use wormhole_bindings::fake::{create_gov_vaa_body, SignVaa, WormholeKeeper}; use wormhole_sdk::core::{Action, GovernancePacket}; use wormhole_sdk::token::Message; -use wormhole_sdk::{Amount, Chain, GOVERNANCE_EMITTER}; +use wormhole_sdk::{relayer, Address, Amount, Chain, GuardianSetInfo, GOVERNANCE_EMITTER}; static INITIALIZER: &str = "initializer"; -static GOV_ADDR: &[u8] = b"GOVERNANCE_ADDRESS"; fn get_config_info(storage: &S) -> ConfigInfo { let key = to_length_prefixed(CONFIG_KEY); @@ -49,10 +48,10 @@ fn do_init(guardians: &[GuardianAddress]) -> OwnedDeps StdResult<()> { let WormholeApp { app, - admin, - user, wormhole_contract, wormhole_keeper, - } = WormholeApp::new(instantiate_with_faker_guardians()); + .. + } = WormholeApp::new_with_faker_guardians(); - let (_, signed_vaa) = sign_vaa_body(wormhole_keeper, create_gov_vaa_body(1, "test")); + let (_, signed_vaa) = create_gov_vaa_body(1, "test").sign_vaa(&wormhole_keeper); // Query verify VAA let parsed_vaa: ParsedVAA = app.wrap().query_wasm_smart( wormhole_contract.clone(), &QueryMsg::VerifyVAA { vaa: signed_vaa, - block_time: 0, + block_time: app.block_info().height, }, )?; @@ -139,21 +137,18 @@ fn queries_test() -> StdResult<()> { fn verify_vaas_query() -> StdResult<()> { let WormholeApp { app, - admin, - user, wormhole_contract, wormhole_keeper, - } = WormholeApp::new(instantiate_with_faker_guardians()); - let (_, signed_vaa) = sign_vaa_body( - wormhole_keeper.clone(), - create_transfer_vaa_body(1, GOVERNANCE_EMITTER), - ); + .. + } = WormholeApp::new_with_faker_guardians(); + let (_, signed_vaa) = + create_transfer_vaa_body(1, GOVERNANCE_EMITTER).sign_vaa(&wormhole_keeper.clone()); let vaa_response: ParsedVAA = app.wrap().query_wasm_smart( wormhole_contract.clone(), &QueryMsg::VerifyVAA { vaa: signed_vaa, - block_time: 0, + block_time: app.block_info().height, }, )?; @@ -196,24 +191,22 @@ fn verify_vaas_query() -> StdResult<()> { ); // Verify a governance VAA - let (_, signed_vaa) = sign_vaa_body( - wormhole_keeper, - create_gov_vaa_body( - 2, - GovernancePacket { - chain: Chain::Osmosis, - action: Action::SetFee { - amount: Amount(*b"00000000000000000000000000000012"), - }, + let (_, signed_vaa) = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Osmosis, + action: Action::SetFee { + amount: Amount(*b"00000000000000000000000000000012"), }, - ), - ); + }, + ) + .sign_vaa(&wormhole_keeper); let vaa_response: ParsedVAA = app.wrap().query_wasm_smart( wormhole_contract.clone(), &QueryMsg::VerifyVAA { vaa: signed_vaa, - block_time: 0, + block_time: app.block_info().height, }, )?; @@ -247,71 +240,675 @@ fn verify_vaas_query() -> StdResult<()> { } #[test] -fn verify_vaas_failure_modes() { - // Single guardian setup- not matching the WormholeKeeper +fn verify_vaa_failure_modes() -> StdResult<()> { let WormholeApp { - app, - admin, - user, + mut app, wormhole_contract, wormhole_keeper, - // Default WormholeApp gives us one guardian - } = WormholeApp::default(); + user, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + let vaa_body = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Terra2, + action: Action::SetFee { + amount: Amount(Uint256::from(1u128).to_be_bytes()), + }, + }, + ); + + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + let vaa_response: StdResult = app.wrap().query_wasm_smart( + wormhole_contract.clone(), + &QueryMsg::VerifyVAA { + vaa: signed_vaa.clone(), + block_time: app.block_info().height, + }, + ); + assert!( + vaa_response.is_ok(), + "VAA signed by the proper guardianset should verify successfully" + ); + + let vaa_response: StdResult = app.wrap().query_wasm_smart( + wormhole_contract.clone(), + &QueryMsg::VerifyVAA { + vaa: signed_vaa, + block_time: u64::MAX, + }, + ); + assert!( + vaa_response.is_err(), + "VAA should fail if the guardian set is past it's expiry \"GuardianSetExpired\"" + ); + + // VAA signed with a nonstandard version listed in the header + let (_, signed_vaa) = sign_vaa_body_version_2(wormhole_keeper.clone(), vaa_body.clone()); + + let vaa_response: StdResult = app.wrap().query_wasm_smart( + wormhole_contract.clone(), + &QueryMsg::VerifyVAA { + vaa: signed_vaa.clone(), + block_time: app.block_info().height, + }, + ); + + assert!( + vaa_response.is_err(), + "VAA should fail \"InvalidVersion\" when signed with a nonstandard version" + ); // VAA signed with a non-matching guardianset - let (_, signed_vaa) = sign_vaa_body( + let (_, signed_vaa) = vaa_body.clone().sign_vaa( // signing with 7 guardians - WormholeKeeper::new(), - create_gov_vaa_body( - 2, - GovernancePacket { - chain: Chain::Osmosis, - action: Action::SetFee { - amount: Amount(*b"00000000000000000000000000000012"), - }, - }, - ), + &WormholeKeeper::new(), ); let vaa_response: StdResult = app.wrap().query_wasm_smart( wormhole_contract.clone(), &QueryMsg::VerifyVAA { vaa: signed_vaa, - block_time: 0, + block_time: app.block_info().height, }, ); assert!( vaa_response.is_err(), - "VAA with more guardians than the established guardian set should fail \"GuardianSignatureError\"" + "VAA with more guardians than the established guardian set should fail \"TooManySignatures\"" ); // VAA signed with a non-matching guardianset let guardian_keys: Vec = vec![]; - let (_, signed_vaa) = sign_vaa_body( + let (_, signed_vaa) = vaa_body.clone().sign_vaa( // signing with 0 guardians - guardian_keys.into(), - create_gov_vaa_body( - 2, - GovernancePacket { - chain: Chain::Osmosis, - action: Action::SetFee { - amount: Amount(*b"00000000000000000000000000000012"), - }, - }, - ), + &guardian_keys.into(), ); let vaa_response: StdResult = app.wrap().query_wasm_smart( wormhole_contract.clone(), &QueryMsg::VerifyVAA { vaa: signed_vaa, - block_time: 0, + block_time: app.block_info().height, + }, + ); + + assert!( + vaa_response.is_err(), + "VAA with fewer guardians than the established guardian set should fail \"NoQuorum\"" + ); + + // VAA signed with a different guardian + let guardian_keys: Vec = vec![SigningKey::from_bytes(&[ + 121, 51, 199, 93, 237, 227, 62, 220, 128, 129, 195, 4, 190, 163, 254, 12, 212, 224, 188, + 76, 141, 242, 229, 121, 192, 5, 161, 176, 136, 99, 83, 53, + ]) + .unwrap()]; + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&guardian_keys.into()); + + let vaa_response: StdResult = app.wrap().query_wasm_smart( + wormhole_contract.clone(), + &QueryMsg::VerifyVAA { + vaa: signed_vaa, + block_time: app.block_info().height, + }, + ); + assert!( + vaa_response.is_err(), + "VAA signed by a guardian not in the established guardian set should fail \"GuardianSignatureError\"" + ); + + // Verifying a VAA that's already been executed should fail + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + // Submit the VAA first + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + assert!( + vaa_response.is_ok(), + "VAA submission should succeed when the VAA has not been executed" + ); + + // Attempt to verify the VAA again after it's been executed + let vaa_response: StdResult = app.wrap().query_wasm_smart( + wormhole_contract.clone(), + &QueryMsg::VerifyVAA { + vaa: signed_vaa, + block_time: app.block_info().height, + }, + ); + assert!( + vaa_response.is_err(), + "VAA that has already been executed should fail \"VAAAlreadyExecuted\"" + ); + + Ok(()) +} + +#[test] +#[ignore] +pub fn update_contract_gov_vaa() -> StdResult<()> { + /// TODO: This test is disabled because it requires cw_multi_test 0.16+ to update the contract admin + use wormchain_ibc_receiver::contract::{ + execute as receiver_execute, instantiate as receiver_instantiate, query as receiver_query, + }; + let WormholeApp { + mut app, + wormhole_contract, + wormhole_keeper, + admin, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + // We have to give the wormhole contract admin rights over itself so that it can migrate itself + let update_admin_response = app.execute( + admin.clone(), + cosmwasm_std::CosmosMsg::Wasm(cosmwasm_std::WasmMsg::UpdateAdmin { + contract_addr: wormhole_contract.to_string(), + admin: wormhole_contract.to_string(), + }), + ); + println!("update admin response: {:?}", update_admin_response); + assert!( + update_admin_response.is_ok(), + "Update Contract Admin should succeed" + ); + + // store the wormchain_ibc_receiver contract so we can migrate to it + let new_code_id = app.store_code(Box::new(ContractWrapper::new( + |deps, env, info, msg| { + receiver_execute(deps, env, info, msg) + .map_err(|anyhow_err| StdError::generic_err(anyhow_err.to_string())) + }, + |deps, env, info, msg| { + receiver_instantiate( + DepsMut { + storage: deps.storage, + api: deps.api, + querier: QuerierWrapper::new(deps.querier.deref()), + }, + env, + info, + msg, + ) + .map_err(|anyhow_err| StdError::generic_err(anyhow_err.to_string())) + }, + |deps, env, msg| { + receiver_query( + Deps { + storage: deps.storage, + api: deps.api, + querier: QuerierWrapper::::new(deps.querier.deref()), + }, + env, + msg, + ) + .map_err(|anyhow_err| StdError::generic_err(anyhow_err.to_string())) + }, + ))); + + let vaa_body = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Terra2, + action: Action::ContractUpgrade { + new_contract: Address(Uint256::from(new_code_id).to_be_bytes()), + }, + }, + ); + + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + // Submit the VAA first + let vaa_response = app.execute_contract( + admin.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + println!("vaa response: {:?}", vaa_response); + assert!( + vaa_response.is_ok(), + "Update Contract VAA submission should succeed" + ); + + Ok(()) +} + +#[test] +#[ignore] +pub fn set_fee_gov_vaa() -> StdResult<()> { + // TODO: set the appropriate MockedApi in the AppBuilder so that PostMessage can be integration tested + // This should be simple once we're on cosmwasm 1.5+ https://docs.rs/cw-multi-test/1.2.0/cw_multi_test/struct.SimpleAddressGenerator.html + let WormholeApp { + mut app, + wormhole_contract, + wormhole_keeper, + user, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + // At this point there is no fee and this should be a free action. + let post_message_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::PostMessage { + message: b"test".into(), + nonce: 1, + }, + &[], + ); + + assert!( + post_message_response.is_ok(), + "Post Message should succeed when there is no fee" + ); + + let vaa_body = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Terra2, + action: Action::SetFee { + amount: Amount(Uint256::from(18u128).to_be_bytes()), + }, + }, + ); + + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + // Submit the VAA first + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + assert!(vaa_response.is_ok(), "SetFee VAA submission should succeed"); + + // At this point there is a fee and this should fail since we aren't paying the fee. + let post_message_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::PostMessage { + message: b"test".into(), + nonce: 1, + }, + &[], + ); + + assert!( + post_message_response.is_err(), + "Post Message should fail \"FeeTooLow\"" + ); + + Ok(()) +} + +#[test] +pub fn set_fee_gov_vaa_2() -> StdResult<()> { + let WormholeApp { + mut app, + wormhole_contract, + wormhole_keeper, + user, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + let vaa_body = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Terra2, + action: Action::SetFee { + amount: Amount(Uint256::from(18u128).to_be_bytes()), + }, + }, + ); + + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + // Submit the VAA first + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), }, + &[], ); - println!("{:?}", vaa_response); + assert!(vaa_response.is_ok(), "SetFee VAA submission should succeed"); + // now query the state and see if the fee has been updated + let get_state_resp: GetStateResponse = app + .wrap() + .query_wasm_smart(wormhole_contract.clone(), &QueryMsg::GetState {})?; + assert_eq!( + get_state_resp.fee.denom, "uluna", + "fee denom does not match" + ); + assert_eq!( + get_state_resp.fee.amount, + Uint128::from(18u128), + "fee amount does not match" + ); + + Ok(()) +} + +#[test] +pub fn submit_vaa_replay_protection() { + let WormholeApp { + mut app, + wormhole_contract, + wormhole_keeper, + user, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + let vaa_body = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Terra2, + action: Action::SetFee { + amount: Amount(Uint256::from(18u128).to_be_bytes()), + }, + }, + ); + + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + // Submit the VAA first + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + assert!(vaa_response.is_ok(), "SetFee VAA submission should succeed"); + + // Submit the VAA again + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + assert!( + vaa_response.is_err(), + "Submitting the same VAA twice should fail" + ); +} + +#[test] +pub fn only_gov_vaas_allowed() { + let WormholeApp { + mut app, + wormhole_contract, + wormhole_keeper, + user, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + let vaa_body = create_transfer_vaa_body(1, Address([100u8; 32])); + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + println!("vaa response: {:?}", vaa_response); assert!( vaa_response.is_err(), - "VAA with fewer guardians than the established guardian set should fail \"GuardianSignatureError\"" + "VAA submission should fail \"InvalidVAA\" when not a governance VAA" ); } + +#[test] +pub fn only_core_module_vaas_allowed() { + let WormholeApp { + mut app, + wormhole_contract, + wormhole_keeper, + user, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + let vaa_body = create_gov_vaa_body( + 1, + relayer::GovernancePacket { + chain: Chain::Terra2, + action: relayer::Action::RegisterChain { + chain: Chain::Solana, + emitter_address: Address([0u8; 32]), + }, + }, + ); + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + assert!( + vaa_response.is_err(), + "VAA submission should fail \"this is not a valid module\" when not a core module VAA" + ); +} + +#[test] +pub fn update_guardian_set() -> StdResult<()> { + let WormholeApp { + mut app, + wormhole_contract, + wormhole_keeper, + user, + .. + } = WormholeApp::new_with_guardians(vec![SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, 206, + 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap()]); + + let vaa_body = create_gov_vaa_body( + 1, + GovernancePacket { + chain: Chain::Terra2, + action: Action::SetFee { + amount: Amount([0u8; 32]), + }, + }, + ); + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + assert!( + vaa_response.is_ok(), + "VAA submission with initial guardian set should succeed" + ); + + // Add a second guardian + let new_guardian_keys = vec![ + SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, + 206, 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 150, 48, 135, 223, 194, 186, 243, 139, 177, 8, 126, 32, 210, 57, 42, 28, 29, 102, 196, + 201, 106, 136, 40, 149, 218, 150, 240, 213, 192, 128, 161, 245, + ]) + .unwrap(), + ]; + + // Query the current guardian set so we know what the next index should be + let guardian_set_response: GuardianSetInfoResponse = app + .wrap() + .query_wasm_smart(wormhole_contract.clone(), &QueryMsg::GuardianSetInfo {})?; + + let invalid_guardian_set_vaa_body = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Terra2, + action: Action::GuardianSetUpgrade { + // This should fail because the index should only increase by one + new_guardian_set_index: guardian_set_response.guardian_set_index + 2, + new_guardian_set: GuardianSetInfo { + addresses: new_guardian_keys + .iter() + .map(|key| -> wormhole_sdk::GuardianAddress { + key.clone().into_guardian_address() + }) + .collect(), + }, + }, + }, + ); + + let (_, signed_guardian_set_update_vaa) = invalid_guardian_set_vaa_body + .clone() + .sign_vaa(&wormhole_keeper); + + let guardian_set_update_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_guardian_set_update_vaa.clone(), + }, + &[], + ); + assert!( + guardian_set_update_response.is_err(), + "UpdateGuardianSet VAA submission should fail \"InvalidGuardianSetIndex\"" + ); + + let update_guardian_set_vaa_body = create_gov_vaa_body( + 2, + GovernancePacket { + chain: Chain::Terra2, + action: Action::GuardianSetUpgrade { + new_guardian_set_index: guardian_set_response.guardian_set_index + 1, + new_guardian_set: GuardianSetInfo { + addresses: new_guardian_keys + .iter() + .map(|key| -> wormhole_sdk::GuardianAddress { + key.clone().into_guardian_address() + }) + .collect(), + }, + }, + }, + ); + + // Sign with the current singular guardian + let (_, signed_guardian_set_update_vaa) = update_guardian_set_vaa_body + .clone() + .sign_vaa(&wormhole_keeper); + + let guardian_set_update_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_guardian_set_update_vaa.clone(), + }, + &[], + ); + assert!( + guardian_set_update_response.is_ok(), + "UpdateGuardianSet VAA submission should succeed" + ); + + let wormhole_keeper: WormholeKeeper = new_guardian_keys.into(); + wormhole_keeper.set_index(guardian_set_response.guardian_set_index + 1); + + let vaa_body = create_gov_vaa_body( + 1, + GovernancePacket { + chain: Chain::Terra2, + action: Action::SetFee { + amount: Amount(Uint256::from(1u128).to_be_bytes()), + }, + }, + ); + let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper); + + let vaa_response = app.execute_contract( + user.clone(), + wormhole_contract.clone(), + &ExecuteMsg::SubmitVAA { + vaa: signed_vaa.clone(), + }, + &[], + ); + + assert!( + vaa_response.is_ok(), + "VAA submission with updated guardian set should succeed" + ); + + let get_state_resp: GetStateResponse = app + .wrap() + .query_wasm_smart(wormhole_contract.clone(), &QueryMsg::GetState {})?; + assert_eq!( + get_state_resp.fee.amount, + Uint128::from(1u128), + "Fee should have been updated to 1uluna" + ); + + Ok(()) +} diff --git a/cosmwasm/contracts/wormhole/src/testing/utils.rs b/cosmwasm/contracts/wormhole/src/testing/utils.rs index 5255d5a9ea..d603b6bfc9 100644 --- a/cosmwasm/contracts/wormhole/src/testing/utils.rs +++ b/cosmwasm/contracts/wormhole/src/testing/utils.rs @@ -1,3 +1,5 @@ +use std::convert::TryInto; + use crate::{ contract::{execute, instantiate, query}, msg::InstantiateMsg, @@ -9,22 +11,23 @@ use k256::ecdsa::SigningKey; use k256::elliptic_curve::sec1::ToEncodedPoint; use serde::Serialize; use tiny_keccak::{Hasher, Keccak}; -use wormhole_bindings::fake::WormholeKeeper; +use wormhole_bindings::fake::{create_vaa_body, default_guardian_keys, WormholeKeeper}; use wormhole_sdk::{ - ibc_receiver::{Action, GovernancePacket}, token::Message, vaa::{Body, Header, Vaa}, Address, Amount, Chain, GOVERNANCE_EMITTER, }; -static GOV_ADDR: &[u8] = b"GOVERNANCE_ADDRESS"; - -pub fn sign_vaa_body(wh: WormholeKeeper, body: Body

) -> (Vaa

, Binary) { +/// Sign a VAA body with version 2 in the header. +pub fn sign_vaa_body_version_2( + wh: WormholeKeeper, + body: Body

, +) -> (Vaa

, Binary) { let data = serde_wormhole::to_vec(&body).unwrap(); let signatures = WormholeKeeper::new().sign(&data); let header = Header { - version: 1, + version: 2, guardian_set_index: wh.guardian_set_index(), signatures, }; @@ -35,48 +38,12 @@ pub fn sign_vaa_body(wh: WormholeKeeper, body: Body

) -> (Vaa

(v, data) } -pub fn create_gov_vaa_body(i: usize, packet: Packet) -> Body { - Body { - timestamp: i as u32, - nonce: i as u32, - emitter_chain: Chain::Solana, - emitter_address: GOVERNANCE_EMITTER, - sequence: i as u64, - consistency_level: 0, - payload: packet, - } -} - -pub fn create_update_channel_chain_packet( - chain_id: Chain, - channel_id: [u8; 64], -) -> GovernancePacket { - GovernancePacket { - chain: Chain::Wormchain, - action: Action::UpdateChannelChain { - channel_id, - chain_id, - }, - } -} - -pub fn create_update_channel_chain_vaa_body( - i: usize, - chain_id: Chain, - channel_id: [u8; 64], -) -> Body { - create_gov_vaa_body(i, create_update_channel_chain_packet(chain_id, channel_id)) -} - pub fn create_transfer_vaa_body(i: usize, emitter_address: Address) -> Body { - Body { - timestamp: i as u32, - nonce: i as u32, - emitter_chain: (i as u16).into(), + create_vaa_body( + i, + i as u16, emitter_address, - sequence: i as u64, - consistency_level: 32, - payload: Message::Transfer { + Message::Transfer { amount: Amount(Uint256::from(i as u128).to_be_bytes()), token_address: Address([(i + 1) as u8; 32]), token_chain: (i as u16).into(), @@ -84,103 +51,7 @@ pub fn create_transfer_vaa_body(i: usize, emitter_address: Address) -> Body InstantiateMsg { - InstantiateMsg { - gov_chain: 0, - gov_address: GOV_ADDR.into(), - initial_guardian_set: GuardianSetInfo { - addresses: guardians.to_vec(), - expiration_time: 100, - }, - guardian_set_expirity: 50, - chain_id: 18, - fee_denom: "uluna".to_string(), - } -} - -/// Instantiate with hardcoded guardian addresses based upon the faker guardian keys in wormhole-bindings -pub fn instantiate_with_faker_guardians() -> InstantiateMsg { - InstantiateMsg { - gov_chain: 0, - gov_address: GOV_ADDR.into(), - initial_guardian_set: GuardianSetInfo { - addresses: vec![ - SigningKey::from_bytes(&[ - 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, - 199, 238, 206, 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, - ]) - .unwrap() - .into(), - SigningKey::from_bytes(&[ - 150, 48, 135, 223, 194, 186, 243, 139, 177, 8, 126, 32, 210, 57, 42, 28, 29, - 102, 196, 201, 106, 136, 40, 149, 218, 150, 240, 213, 192, 128, 161, 245, - ]) - .unwrap() - .into(), - SigningKey::from_bytes(&[ - 121, 51, 199, 93, 237, 227, 62, 220, 128, 129, 195, 4, 190, 163, 254, 12, 212, - 224, 188, 76, 141, 242, 229, 121, 192, 5, 161, 176, 136, 99, 83, 53, - ]) - .unwrap() - .into(), - SigningKey::from_bytes(&[ - 224, 180, 4, 114, 215, 161, 184, 12, 218, 96, 20, 141, 154, 242, 46, 230, 167, - 165, 54, 141, 108, 64, 146, 27, 193, 89, 251, 139, 234, 132, 124, 30, - ]) - .unwrap() - .into(), - SigningKey::from_bytes(&[ - 69, 1, 17, 179, 19, 47, 56, 47, 255, 219, 143, 89, 115, 54, 242, 209, 163, 131, - 225, 30, 59, 195, 217, 141, 167, 253, 6, 95, 252, 52, 7, 223, - ]) - .unwrap() - .into(), - SigningKey::from_bytes(&[ - 181, 3, 165, 125, 15, 200, 155, 56, 157, 204, 105, 221, 203, 149, 215, 175, - 220, 228, 200, 37, 169, 39, 68, 127, 132, 196, 203, 232, 155, 55, 67, 253, - ]) - .unwrap() - .into(), - SigningKey::from_bytes(&[ - 72, 81, 175, 107, 23, 108, 178, 66, 32, 53, 14, 117, 233, 33, 114, 102, 68, 89, - 83, 201, 129, 57, 56, 130, 214, 212, 172, 16, 23, 22, 234, 160, - ]) - .unwrap() - .into(), - ], - expiration_time: 100, - }, - guardian_set_expirity: 50, - chain_id: 18, - fee_denom: "uluna".to_string(), - } -} - -impl From for GuardianAddress { - fn from(value: SigningKey) -> Self { - // Get the public key bytes - let public_key = value.verifying_key().to_encoded_point(false); - let public_key_bytes = public_key.as_bytes(); - - // Skip the first byte (0x04 prefix for uncompressed public keys) - let key_without_prefix = &public_key_bytes[1..]; - - // Hash with Keccak-256 - let mut hasher = Keccak::v256(); - let mut hash = [0u8; 32]; - hasher.update(key_without_prefix); - hasher.finalize(&mut hash); - - // Take last 20 bytes - let address = &hash[12..32]; - - GuardianAddress { - bytes: address.to_vec().into(), - } - } + ) } pub struct WormholeApp { @@ -198,18 +69,63 @@ pub struct WormholeApp { } impl WormholeApp { - pub fn default() -> Self { - create_wormhole_app(None) + pub fn new_with_guardians(guardians: Vec) -> Self { + create_wormhole_app(Some(( + instantiate_with_guardians( + guardians + .iter() + .map(|k| k.clone().into()) + .collect::>() + .as_slice(), + ), + guardians, + ))) + } + pub fn new_with_faker_guardians() -> Self { + create_wormhole_app(Some(( + instantiate_with_guardians( + default_guardian_keys() + .iter() + .map(|k| k.clone().into()) + .collect::>() + .as_slice(), + ), + default_guardian_keys().to_vec(), + ))) } - pub fn new(instantiate_msg: InstantiateMsg) -> Self { - create_wormhole_app(Some(instantiate_msg)) +} + +pub fn instantiate_with_guardians(guardians: &[GuardianAddress]) -> InstantiateMsg { + InstantiateMsg { + gov_chain: Chain::Solana.into(), + gov_address: GOVERNANCE_EMITTER.0.into(), + initial_guardian_set: GuardianSetInfo { + addresses: guardians.to_vec(), + expiration_time: 1571797500, + }, + guardian_set_expirity: 50, + chain_id: Chain::Terra2.into(), + fee_denom: "uluna".to_string(), } } -pub fn create_wormhole_app(instantiate_msg: Option) -> WormholeApp { - let wormhole_keeper = WormholeKeeper::default(); +pub fn create_wormhole_app( + instantiate_msg: Option<(InstantiateMsg, Vec)>, +) -> WormholeApp { + let (instantiate_msg, keys) = instantiate_msg.unwrap_or_else(|| { + let key_bytes = + hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe").expect("Decoding failed"); + ( + instantiate_with_guardians(&[GuardianAddress { + bytes: key_bytes.clone().into(), + }]), + vec![SigningKey::from_bytes(&key_bytes.as_slice()).unwrap()], + ) + }); + + let wormhole_keeper: WormholeKeeper = keys.to_vec().into(); + let mut app = AppBuilder::new_custom() - // .with_wasm(WasmKeeper::default()) .with_custom(wormhole_keeper.clone()) .build(|_, _, _| {}); @@ -224,16 +140,10 @@ pub fn create_wormhole_app(instantiate_msg: Option) -> WormholeA .instantiate_contract( code_id, admin.clone(), - &instantiate_msg.unwrap_or_else(|| { - instantiate_with_guardians(&[GuardianAddress { - bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe") - .expect("Decoding failed") - .into(), - }]) - }), + &instantiate_msg, &[], "cw_wormhole", - None, + Some(admin.to_string()), ) .unwrap(); @@ -245,3 +155,53 @@ pub fn create_wormhole_app(instantiate_msg: Option) -> WormholeA wormhole_keeper, } } + +impl From for GuardianAddress { + fn from(value: SigningKey) -> Self { + // Get the public key bytes + let public_key = value.verifying_key().to_encoded_point(false); + let public_key_bytes = public_key.as_bytes(); + + // Skip the first byte (0x04 prefix for uncompressed public keys) + let key_without_prefix = &public_key_bytes[1..]; + + // Hash with Keccak-256 + let mut hasher = Keccak::v256(); + let mut hash = [0u8; 32]; + hasher.update(key_without_prefix); + hasher.finalize(&mut hash); + + // Take last 20 bytes + let address = &hash[12..32]; + + GuardianAddress { + bytes: address.to_vec().into(), + } + } +} + +pub trait IntoGuardianAddress { + fn into_guardian_address(self) -> wormhole_sdk::GuardianAddress; +} + +impl IntoGuardianAddress for SigningKey { + fn into_guardian_address(self) -> wormhole_sdk::GuardianAddress { + // Get the public key bytes + let public_key = self.verifying_key().to_encoded_point(false); + let public_key_bytes = public_key.as_bytes(); + + // Skip the first byte (0x04 prefix for uncompressed public keys) + let key_without_prefix = &public_key_bytes[1..]; + + // Hash with Keccak-256 + let mut hasher = Keccak::v256(); + let mut hash = [0u8; 32]; + hasher.update(key_without_prefix); + hasher.finalize(&mut hash); + + // Take last 20 bytes + let address: [u8; 20] = hash[12..32].try_into().unwrap(); + + wormhole_sdk::GuardianAddress(address) + } +} diff --git a/cosmwasm/packages/wormhole-bindings/src/fake.rs b/cosmwasm/packages/wormhole-bindings/src/fake.rs index c97771addf..eb47a990ca 100644 --- a/cosmwasm/packages/wormhole-bindings/src/fake.rs +++ b/cosmwasm/packages/wormhole-bindings/src/fake.rs @@ -5,17 +5,61 @@ use cosmwasm_std::{to_binary, Addr, Api, Binary, BlockInfo, CustomQuery, Empty, use cw_multi_test::{AppResponse, CosmosRouter, Module}; use k256::ecdsa::{recoverable, signature::Signer, SigningKey}; use schemars::JsonSchema; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use serde_wormhole::RawMessage; -use wormhole_sdk::vaa::{digest, Header, Signature}; +use wormhole_sdk::{ + token::Message, + vaa::{digest, Body, Header, Signature}, + Address, Chain, Vaa, GOVERNANCE_EMITTER, +}; use crate::WormholeQuery; +pub fn default_guardian_keys() -> [SigningKey; 7] { + [ + SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, 238, + 206, 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 150, 48, 135, 223, 194, 186, 243, 139, 177, 8, 126, 32, 210, 57, 42, 28, 29, 102, 196, + 201, 106, 136, 40, 149, 218, 150, 240, 213, 192, 128, 161, 245, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 121, 51, 199, 93, 237, 227, 62, 220, 128, 129, 195, 4, 190, 163, 254, 12, 212, 224, + 188, 76, 141, 242, 229, 121, 192, 5, 161, 176, 136, 99, 83, 53, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 224, 180, 4, 114, 215, 161, 184, 12, 218, 96, 20, 141, 154, 242, 46, 230, 167, 165, 54, + 141, 108, 64, 146, 27, 193, 89, 251, 139, 234, 132, 124, 30, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 69, 1, 17, 179, 19, 47, 56, 47, 255, 219, 143, 89, 115, 54, 242, 209, 163, 131, 225, + 30, 59, 195, 217, 141, 167, 253, 6, 95, 252, 52, 7, 223, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 181, 3, 165, 125, 15, 200, 155, 56, 157, 204, 105, 221, 203, 149, 215, 175, 220, 228, + 200, 37, 169, 39, 68, 127, 132, 196, 203, 232, 155, 55, 67, 253, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 72, 81, 175, 107, 23, 108, 178, 66, 32, 53, 14, 117, 233, 33, 114, 102, 68, 89, 83, + 201, 129, 57, 56, 130, 214, 212, 172, 16, 23, 22, 234, 160, + ]) + .unwrap(), + ] +} + #[derive(Debug)] struct Inner { index: u32, expiration: u64, - guardians: [SigningKey; 7], + guardians: Vec, } #[derive(Clone, Debug)] @@ -23,47 +67,10 @@ pub struct WormholeKeeper(Rc>); impl WormholeKeeper { pub fn new() -> WormholeKeeper { - let guardians = [ - SigningKey::from_bytes(&[ - 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, - 238, 206, 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, - ]) - .unwrap(), - SigningKey::from_bytes(&[ - 150, 48, 135, 223, 194, 186, 243, 139, 177, 8, 126, 32, 210, 57, 42, 28, 29, 102, - 196, 201, 106, 136, 40, 149, 218, 150, 240, 213, 192, 128, 161, 245, - ]) - .unwrap(), - SigningKey::from_bytes(&[ - 121, 51, 199, 93, 237, 227, 62, 220, 128, 129, 195, 4, 190, 163, 254, 12, 212, 224, - 188, 76, 141, 242, 229, 121, 192, 5, 161, 176, 136, 99, 83, 53, - ]) - .unwrap(), - SigningKey::from_bytes(&[ - 224, 180, 4, 114, 215, 161, 184, 12, 218, 96, 20, 141, 154, 242, 46, 230, 167, 165, - 54, 141, 108, 64, 146, 27, 193, 89, 251, 139, 234, 132, 124, 30, - ]) - .unwrap(), - SigningKey::from_bytes(&[ - 69, 1, 17, 179, 19, 47, 56, 47, 255, 219, 143, 89, 115, 54, 242, 209, 163, 131, - 225, 30, 59, 195, 217, 141, 167, 253, 6, 95, 252, 52, 7, 223, - ]) - .unwrap(), - SigningKey::from_bytes(&[ - 181, 3, 165, 125, 15, 200, 155, 56, 157, 204, 105, 221, 203, 149, 215, 175, 220, - 228, 200, 37, 169, 39, 68, 127, 132, 196, 203, 232, 155, 55, 67, 253, - ]) - .unwrap(), - SigningKey::from_bytes(&[ - 72, 81, 175, 107, 23, 108, 178, 66, 32, 53, 14, 117, 233, 33, 114, 102, 68, 89, 83, - 201, 129, 57, 56, 130, 214, 212, 172, 16, 23, 22, 234, 160, - ]) - .unwrap(), - ]; WormholeKeeper(Rc::new(RefCell::new(Inner { index: 0, expiration: 0, - guardians, + guardians: default_guardian_keys().to_vec(), }))) } @@ -213,6 +220,16 @@ impl Default for WormholeKeeper { } } +impl From> for WormholeKeeper { + fn from(guardians: Vec) -> Self { + WormholeKeeper(Rc::new(RefCell::new(Inner { + index: 0, + expiration: 0, + guardians, + }))) + } +} + impl Module for WormholeKeeper { type ExecT = Empty; type QueryT = WormholeQuery; @@ -256,3 +273,54 @@ impl Module for WormholeKeeper { self.query(request, block) } } + +pub fn create_gov_vaa_body(i: usize, payload: Payload) -> Body { + Body { + timestamp: i as u32, + nonce: i as u32, + emitter_chain: Chain::Solana, + emitter_address: GOVERNANCE_EMITTER, + sequence: i as u64, + consistency_level: 0, + payload, + } +} + +pub fn create_vaa_body( + i: usize, + emitter_chain: impl Into, + emitter_address: Address, + payload: Message, +) -> Body { + Body { + timestamp: i as u32, + nonce: i as u32, + emitter_chain: emitter_chain.into(), + emitter_address, + sequence: i as u64, + consistency_level: 32, + payload, + } +} + +pub trait SignVaa { + fn sign_vaa(self, wh: &WormholeKeeper) -> (Vaa, Binary); +} + +impl SignVaa for Body { + fn sign_vaa(self, wh: &WormholeKeeper) -> (Vaa, Binary) { + let data = serde_wormhole::to_vec(&self).unwrap(); + let signatures = wh.sign(&data); + + let header = Header { + version: 1, + guardian_set_index: wh.guardian_set_index(), + signatures, + }; + + let v: Vaa = (header, self).into(); + let data = serde_wormhole::to_vec(&v).map(From::from).unwrap(); + + (v, data) + } +}