Quellcode durchsuchen

cosmwasm: update cw wormhole rust tests (#4190)

* cosmwasm(cw_wormhole): integration tests- initial implementation

* chore: adding additional vaa parsing tests

* cosmwasm: added cw_wormhole integration tests and some code cleanup

cosmwasm: reverted contract changes

* cosmwasm: refactored the to guardianAddress logic

* wormchain: lower ict setup params to improve stability
Kaku vor 11 Monaten
Ursprung
Commit
7ef11596a0

+ 57 - 4
cosmwasm/Cargo.lock

@@ -487,15 +487,15 @@ dependencies = [
 
 [[package]]
 name = "cw-multi-test"
-version = "0.13.4"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e"
+checksum = "ca153120cf5b91af88be106b0c6c0263423d959bc813b1592982c02c4691a4ae"
 dependencies = [
  "anyhow",
  "cosmwasm-std",
  "cosmwasm-storage",
- "cw-storage-plus 0.13.4",
- "cw-utils 0.13.4",
+ "cw-storage-plus 0.14.0",
+ "cw-utils 0.14.0",
  "derivative",
  "itertools",
  "prost 0.9.0",
@@ -515,6 +515,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "cw-storage-plus"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c8b264257c4f44c49b7ce09377af63aa040768ecd3fd7bdd2d48a09323a1e90"
+dependencies = [
+ "cosmwasm-std",
+ "schemars",
+ "serde",
+]
+
 [[package]]
 name = "cw-storage-plus"
 version = "1.1.0"
@@ -538,6 +549,20 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "cw-utils"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e"
+dependencies = [
+ "cosmwasm-std",
+ "cw2 0.14.0",
+ "schemars",
+ "semver",
+ "serde",
+ "thiserror",
+]
+
 [[package]]
 name = "cw-utils"
 version = "1.0.1"
@@ -565,6 +590,18 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "cw2"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6"
+dependencies = [
+ "cosmwasm-std",
+ "cw-storage-plus 0.14.0",
+ "schemars",
+ "serde",
+]
+
 [[package]]
 name = "cw2"
 version = "1.1.0"
@@ -1958,6 +1995,15 @@ dependencies = [
  "syn 2.0.23",
 ]
 
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
 [[package]]
 name = "tinyvec"
 version = "1.6.0"
@@ -2633,13 +2679,20 @@ dependencies = [
  "cosmwasm-schema",
  "cosmwasm-std",
  "cosmwasm-storage",
+ "cw-multi-test",
  "generic-array",
  "hex",
  "k256",
  "schemars",
  "serde",
+ "serde-json-wasm 0.4.1",
+ "serde_wormhole",
  "sha3 0.9.1",
  "thiserror",
+ "tiny-keccak",
+ "wormchain-ibc-receiver",
+ "wormhole-bindings",
+ "wormhole-vaas-serde",
 ]
 
 [[package]]

+ 1 - 1
cosmwasm/contracts/global-accountant/Cargo.toml

@@ -32,6 +32,6 @@ wormhole-sdk = { workspace = true, features = ["schemars"] }
 
 [dev-dependencies]
 anyhow = { version = "1", features = ["backtrace"] }
-cw-multi-test = "0.13.2"
+cw-multi-test = "0.14"
 serde-json-wasm = "0.4"
 wormhole-bindings = { version = "0.1", features = ["fake"] }

+ 1 - 1
cosmwasm/contracts/ntt-global-accountant/Cargo.toml

@@ -35,6 +35,6 @@ wormhole-sdk = { workspace = true, features = ["schemars"] }
 
 [dev-dependencies]
 anyhow = { version = "1", features = ["backtrace"] }
-cw-multi-test = "0.13.2"
+cw-multi-test = "0.14"
 serde-json-wasm = "0.4"
 wormhole-bindings = { version = "0.1", features = ["fake"] }

+ 1 - 1
cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml

@@ -23,7 +23,7 @@ wormhole-sdk = { workspace = true, features = ["schemars"] }
 serde_wormhole.workspace = true
 
 [dev-dependencies]
-cw-multi-test = "0.13.2"
+cw-multi-test = "0.14"
 serde-json-wasm = "0.4"
 wormhole-bindings = { version = "0.1.0", features=["fake"] }
 serde = { version = "1.0.137", default-features = false, features = ["derive"] }

+ 9 - 0
cosmwasm/contracts/wormhole/Cargo.toml

@@ -29,3 +29,12 @@ k256 = { version = "0.11", default-features = false, features = ["ecdsa"] }
 sha3 = { version = "0.9.1", default-features = false }
 generic-array = { version = "0.14.4" }
 hex = "0.4.2"
+
+[dev-dependencies]
+cw-multi-test = "0.14"
+serde_wormhole.workspace = true
+wormhole-sdk.workspace = true
+wormhole-bindings = { version = "0.1", features = ["fake"] }
+tiny-keccak = { version = "2.0", features = ["keccak"] }
+serde-json-wasm = "0.4"
+wormchain-ibc-receiver = { path = "../wormchain-ibc-receiver" }

+ 912 - 0
cosmwasm/contracts/wormhole/src/testing/integration.rs

@@ -0,0 +1,912 @@
+use crate::msg::{ExecuteMsg, GetStateResponse, GuardianSetInfoResponse};
+use crate::testing::utils::{
+    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},
+};
+use cosmwasm_std::{
+    from_slice,
+    testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage},
+    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::{ContractWrapper, Executor};
+use k256::ecdsa::SigningKey;
+use serde_wormhole::RawMessage;
+use std::ops::Deref;
+use wormhole_bindings::fake::{create_gov_vaa_body, SignVaa, WormholeKeeper};
+use wormhole_sdk::core::{Action, GovernancePacket};
+use wormhole_sdk::token::Message;
+use wormhole_sdk::{relayer, Address, Amount, Chain, GuardianSetInfo, GOVERNANCE_EMITTER};
+
+static INITIALIZER: &str = "initializer";
+
+fn get_config_info<S: Storage>(storage: &S) -> ConfigInfo {
+    let key = to_length_prefixed(CONFIG_KEY);
+    let data = storage.get(&key).expect("data should exist");
+    from_slice(&data).expect("invalid data")
+}
+
+fn do_init(guardians: &[GuardianAddress]) -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
+    let mut deps = mock_dependencies();
+    let init_msg = instantiate_with_guardians(guardians);
+    let env = mock_env();
+    let info = mock_info(INITIALIZER, &[]);
+    let res: Response = instantiate(deps.as_mut(), env, info, init_msg).unwrap();
+    assert_eq!(0, res.messages.len());
+
+    // query the store directly
+    assert_eq!(
+        get_config_info(&deps.storage),
+        ConfigInfo {
+            guardian_set_index: 0,
+            guardian_set_expirity: 50,
+            gov_chain: Chain::Solana.into(),
+            gov_address: GOVERNANCE_EMITTER.0.to_vec(),
+            fee: Coin::new(0, "uluna"),
+            chain_id: Chain::Terra2.into(),
+            fee_denom: "uluna".to_string(),
+        }
+    );
+    deps
+}
+
+#[test]
+fn init_works() {
+    let guardians = [GuardianAddress {
+        bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe")
+            .expect("Decoding failed")
+            .into(),
+    }];
+    let _deps = do_init(&guardians);
+}
+
+#[test]
+fn queries_test() -> StdResult<()> {
+    let WormholeApp {
+        app,
+        wormhole_contract,
+        wormhole_keeper,
+        ..
+    } = WormholeApp::new_with_faker_guardians();
+
+    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: app.block_info().height,
+        },
+    )?;
+
+    let test_payload = serde_wormhole::from_slice::<String>(parsed_vaa.payload.as_slice());
+    assert!(test_payload.is_ok(), "failed to parse test payload");
+    assert_eq!(test_payload.unwrap(), "test", "test payload does not match");
+
+    assert_eq!(parsed_vaa.version, 1, "version does not match");
+    assert_eq!(
+        parsed_vaa.guardian_set_index, 0,
+        "guardian set index does not match"
+    );
+
+    // Query guardian set info
+    let guardian_set_response: GuardianSetInfoResponse = app
+        .wrap()
+        .query_wasm_smart(wormhole_contract.clone(), &QueryMsg::GuardianSetInfo {})?;
+
+    assert_eq!(
+        guardian_set_response.guardian_set_index, 0u32,
+        "guardian set index does not match"
+    );
+    assert_eq!(
+        guardian_set_response.addresses.len(),
+        7,
+        "guardian set length does not match"
+    );
+
+    // Query get state
+    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(0u128),
+        "fee amount does not match"
+    );
+
+    // TODO: set the appropriate MockedApi in the AppBuilder so that QueryAddressHex 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
+
+    Ok(())
+}
+
+#[test]
+fn verify_vaas_query() -> StdResult<()> {
+    let WormholeApp {
+        app,
+        wormhole_contract,
+        wormhole_keeper,
+        ..
+    } = 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: app.block_info().height,
+        },
+    )?;
+
+    assert_eq!(vaa_response.version, 1, "version does not match");
+    assert_eq!(
+        vaa_response.guardian_set_index, 0,
+        "guardian set index does not match"
+    );
+    assert_eq!(vaa_response.timestamp, 1, "timestamp does not match");
+    assert_eq!(vaa_response.nonce, 1, "nonce does not match");
+    assert_eq!(vaa_response.len_signers, 7, "len signers does not match");
+    assert_eq!(
+        vaa_response.emitter_chain, 1,
+        "emitter chain does not match"
+    );
+    assert_eq!(vaa_response.sequence, 1, "sequence does not match");
+    assert_eq!(
+        vaa_response.consistency_level, 32,
+        "consistency level does not match"
+    );
+    assert_eq!(
+        vaa_response.emitter_address.as_slice(),
+        GOVERNANCE_EMITTER.0.as_slice(),
+        "emitter address does not match"
+    );
+
+    let transfer_payload =
+        serde_wormhole::from_slice::<Message<&RawMessage>>(vaa_response.payload.as_slice());
+
+    assert!(transfer_payload.is_ok(), "failed to parse transfer payload");
+    assert!(
+        matches!(
+            transfer_payload.unwrap(),
+            Message::Transfer {
+                token_chain: Chain::Solana,
+                ..
+            }
+        ),
+        "unexpected payload"
+    );
+
+    // Verify a governance VAA
+    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: app.block_info().height,
+        },
+    )?;
+
+    assert_eq!(
+        vaa_response.version, 1,
+        "governance vaa version does not match"
+    );
+    assert_eq!(
+        vaa_response.guardian_set_index, 0,
+        "governance vaa guardian set index does not match"
+    );
+    let governance_payload =
+        serde_wormhole::from_slice::<GovernancePacket>(vaa_response.payload.as_slice());
+
+    assert!(
+        governance_payload.is_ok(),
+        "failed to parse governance payload"
+    );
+    assert!(
+        matches!(
+            governance_payload.unwrap(),
+            GovernancePacket {
+                action: Action::SetFee { .. },
+                chain: Chain::Osmosis,
+            }
+        ),
+        "unexpected payload"
+    );
+
+    Ok(())
+}
+
+#[test]
+fn verify_vaa_failure_modes() -> 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(1u128).to_be_bytes()),
+            },
+        },
+    );
+
+    let (_, signed_vaa) = vaa_body.clone().sign_vaa(&wormhole_keeper);
+    let vaa_response: StdResult<ParsedVAA> = 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<ParsedVAA> = 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<ParsedVAA> = 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) = vaa_body.clone().sign_vaa(
+        // signing with 7 guardians
+        &WormholeKeeper::new(),
+    );
+
+    let vaa_response: StdResult<ParsedVAA> = 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 with more guardians than the established guardian set should fail \"TooManySignatures\""
+    );
+
+    // VAA signed with a non-matching guardianset
+    let guardian_keys: Vec<SigningKey> = vec![];
+    let (_, signed_vaa) = vaa_body.clone().sign_vaa(
+        // signing with 0 guardians
+        &guardian_keys.into(),
+    );
+
+    let vaa_response: StdResult<ParsedVAA> = 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 with fewer guardians than the established guardian set should fail \"NoQuorum\""
+    );
+
+    // VAA signed with a different guardian
+    let guardian_keys: Vec<SigningKey> = 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<ParsedVAA> = 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<ParsedVAA> = 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(),
+        }),
+    );
+
+    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::<Empty>::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(),
+        },
+        &[],
+    );
+
+    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(),
+        },
+        &[],
+    );
+    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(),
+        },
+        &[],
+    );
+
+    assert!(
+        vaa_response.is_err(),
+        "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(())
+}

+ 2 - 0
cosmwasm/contracts/wormhole/src/testing/mod.rs

@@ -1 +1,3 @@
+pub mod integration;
 mod tests;
+pub mod utils;

+ 196 - 0
cosmwasm/contracts/wormhole/src/testing/utils.rs

@@ -0,0 +1,196 @@
+use std::convert::TryInto;
+
+use crate::{
+    contract::{execute, instantiate, query},
+    msg::InstantiateMsg,
+    state::{GuardianAddress, GuardianSetInfo},
+};
+use cosmwasm_std::{Addr, Binary, Uint256};
+use cw_multi_test::{App, AppBuilder, ContractWrapper, Executor, WasmKeeper};
+use k256::ecdsa::SigningKey;
+use k256::elliptic_curve::sec1::ToEncodedPoint;
+use serde::Serialize;
+use tiny_keccak::{Hasher, Keccak};
+use wormhole_bindings::fake::{create_vaa_body, default_guardian_keys, WormholeKeeper};
+use wormhole_sdk::{
+    token::Message,
+    vaa::{Body, Header, Vaa},
+    Address, Amount, Chain, GOVERNANCE_EMITTER,
+};
+
+/// Sign a VAA body with version 2 in the header.
+pub fn sign_vaa_body_version_2<P: Serialize>(
+    wh: WormholeKeeper,
+    body: Body<P>,
+) -> (Vaa<P>, Binary) {
+    let data = serde_wormhole::to_vec(&body).unwrap();
+    let signatures = WormholeKeeper::new().sign(&data);
+
+    let header = Header {
+        version: 2,
+        guardian_set_index: wh.guardian_set_index(),
+        signatures,
+    };
+
+    let v: Vaa<P> = (header, body).into();
+    let data = serde_wormhole::to_vec(&v).map(From::from).unwrap();
+
+    (v, data)
+}
+
+pub fn create_transfer_vaa_body(i: usize, emitter_address: Address) -> Body<Message> {
+    create_vaa_body(
+        i,
+        i as u16,
+        emitter_address,
+        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(),
+            recipient: Address([i as u8; 32]),
+            recipient_chain: ((i + 2) as u16).into(),
+            fee: Amount([0u8; 32]),
+        },
+    )
+}
+
+pub struct WormholeApp {
+    pub app: App<
+        cw_multi_test::BankKeeper,
+        cosmwasm_std::testing::MockApi,
+        cosmwasm_std::MemoryStorage,
+        WormholeKeeper,
+        WasmKeeper<cosmwasm_std::Empty, wormhole_bindings::WormholeQuery>,
+    >,
+    pub admin: Addr,
+    pub user: Addr,
+    pub wormhole_contract: Addr,
+    pub wormhole_keeper: WormholeKeeper,
+}
+
+impl WormholeApp {
+    pub fn new_with_guardians(guardians: Vec<SigningKey>) -> Self {
+        create_wormhole_app(Some((
+            instantiate_with_guardians(
+                guardians
+                    .iter()
+                    .map(|k| k.clone().into())
+                    .collect::<Vec<GuardianAddress>>()
+                    .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::<Vec<GuardianAddress>>()
+                    .as_slice(),
+            ),
+            default_guardian_keys().to_vec(),
+        )))
+    }
+}
+
+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<(InstantiateMsg, Vec<SigningKey>)>,
+) -> 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_custom(wormhole_keeper.clone())
+        .build(|_, _, _| {});
+
+    let admin = Addr::unchecked("admin");
+    let user = Addr::unchecked("user");
+
+    let cw_wormhole_wrapper = ContractWrapper::new_with_empty(execute, instantiate, query);
+
+    let code_id = app.store_code(Box::new(cw_wormhole_wrapper));
+
+    let contract_addr = app
+        .instantiate_contract(
+            code_id,
+            admin.clone(),
+            &instantiate_msg,
+            &[],
+            "cw_wormhole",
+            Some(admin.to_string()),
+        )
+        .unwrap();
+
+    WormholeApp {
+        app,
+        admin,
+        user,
+        wormhole_contract: contract_addr,
+        wormhole_keeper,
+    }
+}
+
+impl From<SigningKey> 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 {
+        let guardian: GuardianAddress = self.into();
+
+        // Take last 20 bytes
+        let address: [u8; 20] = guardian.bytes.0.try_into().unwrap();
+
+        wormhole_sdk::GuardianAddress(address)
+    }
+}

+ 0 - 65
cosmwasm/contracts/wormhole/tests/integration.rs

@@ -1,65 +0,0 @@
-use cosmwasm_std::{
-    from_slice,
-    testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage},
-    Coin, OwnedDeps, Response, Storage,
-};
-use cosmwasm_storage::to_length_prefixed;
-
-use cw_wormhole::{
-    contract::instantiate,
-    msg::InstantiateMsg,
-    state::{ConfigInfo, GuardianAddress, GuardianSetInfo, CONFIG_KEY},
-};
-
-static INITIALIZER: &str = "initializer";
-static GOV_ADDR: &[u8] = b"GOVERNANCE_ADDRESS";
-
-fn get_config_info<S: Storage>(storage: &S) -> ConfigInfo {
-    let key = to_length_prefixed(CONFIG_KEY);
-    let data = storage.get(&key).expect("data should exist");
-    from_slice(&data).expect("invalid data")
-}
-
-fn do_init(guardians: &[GuardianAddress]) -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
-    let mut deps = mock_dependencies();
-    let init_msg = 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(),
-    };
-    let env = mock_env();
-    let info = mock_info(INITIALIZER, &[]);
-    let res: Response = instantiate(deps.as_mut(), env, info, init_msg).unwrap();
-    assert_eq!(0, res.messages.len());
-
-    // query the store directly
-    assert_eq!(
-        get_config_info(&deps.storage),
-        ConfigInfo {
-            guardian_set_index: 0,
-            guardian_set_expirity: 50,
-            gov_chain: 0,
-            gov_address: GOV_ADDR.to_vec(),
-            fee: Coin::new(0, "uluna"),
-            chain_id: 18,
-            fee_denom: "uluna".to_string(),
-        }
-    );
-    deps
-}
-
-#[test]
-fn init_works() {
-    let guardians = [GuardianAddress {
-        bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe")
-            .expect("Decoding failed")
-            .into(),
-    }];
-    let _deps = do_init(&guardians);
-}

+ 1 - 1
cosmwasm/packages/wormhole-bindings/Cargo.toml

@@ -14,6 +14,6 @@ cosmwasm-std = "1"
 schemars = "0.8.8"
 serde = { version = "1.0.137", default-features = false, features = ["derive"] }
 serde_wormhole = { workspace = true, optional = true }
-cw-multi-test = { version = "0.13.2", optional = true }
+cw-multi-test = { version = "0.14", optional = true }
 k256 = { version = "0.11", optional = true, features = ["ecdsa", "keccak256"] }
 wormhole-sdk.workspace = true

+ 109 - 41
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<SigningKey>,
 }
 
 #[derive(Clone, Debug)]
@@ -23,47 +67,10 @@ pub struct WormholeKeeper(Rc<RefCell<Inner>>);
 
 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<Vec<SigningKey>> for WormholeKeeper {
+    fn from(guardians: Vec<SigningKey>) -> 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<Payload>(i: usize, payload: Payload) -> Body<Payload> {
+    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<Chain>,
+    emitter_address: Address,
+    payload: Message,
+) -> Body<Message> {
+    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<M> {
+    fn sign_vaa(self, wh: &WormholeKeeper) -> (Vaa<M>, Binary);
+}
+
+impl<M: Serialize> SignVaa<M> for Body<M> {
+    fn sign_vaa(self, wh: &WormholeKeeper) -> (Vaa<M>, 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<M> = (header, self).into();
+        let data = serde_wormhole::to_vec(&v).map(From::from).unwrap();
+
+        (v, data)
+    }
+}

+ 5 - 5
wormchain/interchaintest/setup.go

@@ -132,11 +132,11 @@ func BuildInterchain(t *testing.T, chains []ibc.Chain) (context.Context, ibc.Rel
 	})
 
 	err := ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{
-		TestName:          t.Name(),
-		Client:            client,
-		NetworkID:         network,
-		SkipPathCreation:  false,
-		BlockDatabaseFile: interchaintest.DefaultBlockDatabaseFilepath(),
+		TestName:         t.Name(),
+		Client:           client,
+		NetworkID:        network,
+		SkipPathCreation: false,
+		// BlockDatabaseFile: interchaintest.DefaultBlockDatabaseFilepath(),
 	})
 	require.NoError(t, err)
 

+ 1 - 1
wormchain/interchaintest/upgrade_test.go

@@ -40,7 +40,7 @@ import (
 //   - Verify asset 1 balance of gaia user 1, osmo user 1, osmo user 2, and cw20 contract total supply
 func TestUpgrade(t *testing.T) {
 	// Base setup
-	numVals := 5
+	numVals := 3
 	guardians := guardians.CreateValSet(t, numVals)
 	chains := CreateChains(t, "v2.18.1", *guardians)
 	ctx, r, eRep, client := BuildInterchain(t, chains)