Skip to content

Commit

Permalink
cosmwasm: add wormchain-ibc-receiver and wormhole-ibc contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
nik-suri committed Apr 6, 2023
1 parent 5f63360 commit b8e5f5c
Show file tree
Hide file tree
Showing 18 changed files with 904 additions and 1 deletion.
38 changes: 38 additions & 0 deletions cosmwasm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions cosmwasm/Cargo.toml
Expand Up @@ -10,6 +10,8 @@ members = [
"contracts/global-accountant",
"packages/wormhole-bindings",
"packages/cw_transcode",
"contracts/wormhole-ibc",
"contracts/wormchain-ibc-receiver"
]

# Needed to prevent unwanted feature unification between normal builds and dev builds. See
Expand Down Expand Up @@ -37,3 +39,5 @@ global-accountant = { path = "contracts/global-accountant" }
wormhole-bindings = { path = "packages/wormhole-bindings" }
wormhole-bridge-terra-2 = { path = "contracts/wormhole" }
wormhole-core = { path = "../sdk/rust/core" }
wormchain-ibc-receiver = { path = "contracts/wormchain-ibc-receiver" }
wormhole-ibc = { path = "contracts/wormhole-ibc" }
5 changes: 5 additions & 0 deletions cosmwasm/contracts/wormchain-ibc-receiver/.cargo/config
@@ -0,0 +1,5 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
unit-test = "test --lib --features backtraces"
integration-test = "test --test integration"
23 changes: 23 additions & 0 deletions cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml
@@ -0,0 +1,23 @@
[package]
name = "wormchain-ibc-receiver"
version = "0.1.0"
authors = ["Wormhole Project Contributors"]
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
backtraces = ["cosmwasm-std/backtraces"]

[dependencies]
cosmwasm-std = { version = "1.0.0", features = ["ibc3"] }
cosmwasm-schema = "1"
cw-storage-plus = "0.13.2"
cw2 = "0.13.2"
anyhow = "1"
semver = "1.0.16"
thiserror = "1.0.31"
wormhole-bindings = "0.1.0"
wormhole-core = { version = "0.1.0", features = ["schemars"] }
serde_wormhole = "0.1.0"
208 changes: 208 additions & 0 deletions cosmwasm/contracts/wormchain-ibc-receiver/src/contract.rs
@@ -0,0 +1,208 @@
use anyhow::{ensure, Context};
use cosmwasm_std::{entry_point, to_binary, Binary, Deps, Empty, Event, StdError, StdResult};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Order, Response};
use cw2::{get_contract_version, set_contract_version};
use semver::Version;
use serde_wormhole::RawMessage;
use wormhole::ibc_receiver::{Action, GovernancePacket};
use wormhole::vaa::{Body, Header};
use wormhole::Chain;
use wormhole_bindings::WormholeQuery;

use crate::error::ContractError;
use crate::msg::{AllChainConnectionsResponse, ChainConnectionResponse, ExecuteMsg, QueryMsg};
use crate::state::CHAIN_CONNECTIONS;

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:wormchain-ibc-receiver";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
_msg: Empty,
) -> Result<Response, anyhow::Error> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)
.context("failed to set contract version")?;

Ok(Response::new()
.add_attribute("action", "instantiate")
.add_attribute("owner", info.sender)
.add_attribute("version", CONTRACT_VERSION))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, anyhow::Error> {
let ver = get_contract_version(deps.storage)?;
// ensure we are migrating from an allowed contract
if ver.contract != CONTRACT_NAME {
return Err(StdError::generic_err("Can only upgrade from same type").into());
}

// ensure we are migrating to a newer version
let saved_version =
Version::parse(&ver.version).context("could not parse saved contract version")?;
let new_version =
Version::parse(CONTRACT_VERSION).context("could not parse new contract version")?;
if saved_version >= new_version {
return Err(StdError::generic_err("Cannot upgrade from a newer or equal version").into());
}

// set the new version
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

Ok(Response::default())
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut<WormholeQuery>,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, anyhow::Error> {
match msg {
ExecuteMsg::SubmitUpdateChainConnection { vaas } => submit_vaas(deps, info, vaas),
}
}

fn submit_vaas(
mut deps: DepsMut<WormholeQuery>,
info: MessageInfo,
vaas: Vec<Binary>,
) -> Result<Response, anyhow::Error> {
let evts = vaas
.into_iter()
.map(|v| handle_vaa(deps.branch(), v))
.collect::<anyhow::Result<Vec<_>>>()?;
Ok(Response::new()
.add_attribute("action", "submit_vaas")
.add_attribute("owner", info.sender)
.add_events(evts))
}

fn handle_vaa(deps: DepsMut<WormholeQuery>, vaa: Binary) -> anyhow::Result<Event> {
// parse the VAA header and data
let (header, data) = serde_wormhole::from_slice::<(Header, &RawMessage)>(&vaa)
.context("failed to parse VAA header")?;

// Must be a version 1 VAA
ensure!(header.version == 1, "unsupported VAA version");

// call into wormchain to verify the VAA
deps.querier
.query::<Empty>(&WormholeQuery::VerifyVaa { vaa: vaa.clone() }.into())
.context(ContractError::VerifyQuorum)?;

// parse the VAA body
let body = serde_wormhole::from_slice::<Body<&RawMessage>>(data)
.context("failed to parse VAA body")?;

// validate this is a governance VAA
ensure!(
body.emitter_chain == Chain::Solana && body.emitter_address == wormhole::GOVERNANCE_EMITTER,
"not a governance VAA"
);

// parse the governance packet
let govpacket: GovernancePacket =
serde_wormhole::from_slice(body.payload).context("failed to parse governance packet")?;

// validate the governance VAA is directed to wormchain
ensure!(
govpacket.chain == Chain::Wormchain,
"this governance VAA is for another chain"
);

// match the governance action and execute the corresponding logic
match govpacket.action {
Action::UpdateChainConnection {
connection_id,
chain_id,
} => {
// update storage with the mapping
CHAIN_CONNECTIONS
.save(deps.storage, connection_id.to_string(), &chain_id.into())
.context("failed to save chain connection")?;
Ok(Event::new("UpdateChainConnection")
.add_attribute("chain_id", chain_id.to_string())
.add_attribute("connection_id", connection_id.to_string()))
}
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::ChainConnection { connection_id } => {
query_chain_connection(deps, connection_id).and_then(|resp| to_binary(&resp))
}
QueryMsg::AllChainConnections {} => {
query_all_chain_connections(deps).and_then(|resp| to_binary(&resp))
}
}
}

fn query_chain_connection(deps: Deps, connection_id: Binary) -> StdResult<ChainConnectionResponse> {
CHAIN_CONNECTIONS
.load(deps.storage, connection_id.to_string())
.map(|chain_id| ChainConnectionResponse { chain_id })
}

fn query_all_chain_connections(deps: Deps) -> StdResult<AllChainConnectionsResponse> {
CHAIN_CONNECTIONS
.range(deps.storage, None, None, Order::Ascending)
.map(|res| {
res.map(|(connection_id, chain_id)| {
(Binary::from(Vec::<u8>::from(connection_id)), chain_id)
})
})
.collect::<StdResult<Vec<_>>>()
.map(|chain_connections| AllChainConnectionsResponse { chain_connections })
}

#[cfg(test)]
mod tests {
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info},
Empty,
};
use cw2::get_contract_version;

use super::{instantiate, CONTRACT_NAME, CONTRACT_VERSION};

#[test]
fn instantiate_works() {
let mut deps = mock_dependencies();

const SENDER: &str = "creator";
let info = mock_info(SENDER, &[]);
let res = instantiate(deps.as_mut(), mock_env(), info, Empty {}).unwrap();

// the response should have 0 messages and 3 attributes
assert_eq!(0, res.messages.len());
assert_eq!(3, res.attributes.len());

// validate the attributes and their values
res.attributes.iter().for_each(|a| {
let value = if a.key == "action" {
"instantiate"
} else if a.key == "owner" {
SENDER
} else if a.key == "version" {
CONTRACT_VERSION
} else {
panic!("invalid attribute key");
};

assert_eq!(a.value, value);
});

// check that contract version & name have been set
let contract_version = get_contract_version(deps.as_ref().storage).unwrap();
assert_eq!(CONTRACT_NAME, contract_version.contract);
assert_eq!(CONTRACT_VERSION, contract_version.version);
}
}
7 changes: 7 additions & 0 deletions cosmwasm/contracts/wormchain-ibc-receiver/src/error.rs
@@ -0,0 +1,7 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ContractError {
#[error("failed to verify quorum")]
VerifyQuorum,
}

0 comments on commit b8e5f5c

Please sign in to comment.