sdk/rust: Add serde-based struct defintions

Refactor the core crate to add serde-based struct definitions for the
various messages used by the different wormhole smart contracts.  This
will also make it easier to use alternate data formats (like json) for
client-side tooling.

Co-authored-by: Reisen <reisen@morphism.org>
This commit is contained in:
Chirantan Ekbote 2022-11-14 18:55:39 +09:00 committed by Chirantan Ekbote
parent 92cb3d28a3
commit d157c7bf67
24 changed files with 2884 additions and 2309 deletions

134
sdk/rust/Cargo.lock generated
View File

@ -3,20 +3,19 @@
version = 3
[[package]]
name = "block-buffer"
version = "0.9.0"
name = "anyhow"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding",
"generic-array",
]
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "block-padding"
version = "0.2.1"
name = "block-buffer"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
@ -31,33 +30,23 @@ dependencies = [
]
[[package]]
name = "byteorder"
version = "1.4.3"
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "fixed-hash"
version = "0.7.0"
name = "digest"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
dependencies = [
"static_assertions",
"block-buffer",
"crypto-common",
]
[[package]]
@ -70,12 +59,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "itoa"
version = "1.0.4"
@ -94,44 +77,12 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "primitive-types"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e"
dependencies = [
"fixed-hash",
"uint",
]
[[package]]
name = "proc-macro2"
version = "1.0.47"
@ -156,6 +107,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "serde"
version = "1.0.147"
@ -185,6 +142,17 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.9"
@ -209,22 +177,14 @@ dependencies = [
[[package]]
name = "sha3"
version = "0.9.1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9"
dependencies = [
"block-buffer",
"digest",
"keccak",
"opaque-debug",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.103"
@ -262,18 +222,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "uint"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601"
dependencies = [
"byteorder",
"crunchy",
"hex",
"static_assertions",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
@ -290,10 +238,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "wormhole-core"
version = "0.1.0"
dependencies = [
"anyhow",
"bstr",
"byteorder",
"hex",
"nom",
"primitive-types",
"serde",
"serde_json",
"serde_wormhole",
"sha3",
]

View File

@ -1,20 +1,17 @@
[package]
name = "wormhole-core"
version = "0.1.0"
edition = "2018"
edition = "2021"
[features]
[lib]
name = "wormhole"
[dependencies]
byteorder = "*"
hex = "*"
nom = { version="7", default-features=false, features=["alloc"] }
primitive-types = { version="0.9.0", default-features=false }
sha3 = "0.9.1"
bstr = "*"
anyhow = "1"
bstr = { version = "1", features = ["serde"] }
serde = { version = "1", default-features = false, features = ["alloc", "derive"] }
serde_wormhole = "0.1.0"
sha3 = "0.10.4"
[dev-dependencies]
byteorder = "*"
hex = "*"
serde_json = "1"

View File

@ -0,0 +1,112 @@
//! A module for serializing/deserializing a `BString` as a fixed-width 32 byte array.
use std::{convert::identity, fmt, iter::repeat};
use bstr::BString;
use serde::{
de::{Error as DeError, SeqAccess, Visitor},
ser::{Error as SerError, SerializeTuple},
Deserializer, Serializer,
};
pub fn serialize<T, S>(value: T, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
let v = value.as_ref();
let l = v.len();
if l > 32 {
return Err(S::Error::custom(format_args!(
"value is too large ({l} bytes); max 32",
)));
}
let mut tup = serializer.serialize_tuple(32)?;
for e in repeat(&0u8).take(32 - l).chain(v) {
tup.serialize_element(e)?;
}
tup.end()
}
struct ArrayStringVisitor;
impl<'de> Visitor<'de> for ArrayStringVisitor {
type Value = BString;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("an array of 32 bytes")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut buf = Vec::with_capacity(32);
for i in 0..32 {
let e = seq
.next_element()
.map(|e| e.ok_or_else(|| A::Error::invalid_length(i, &self)))
.and_then(identity)?; // TODO: Replace with Result::flatten once stabilized.
if e == 0 && buf.is_empty() {
// Skip all leading zeroes.
continue;
}
buf.push(e);
}
Ok(BString::from(buf))
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<BString, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_tuple(32, ArrayStringVisitor)
}
#[cfg(test)]
mod test {
use bstr::BString;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
#[repr(transparent)]
struct MyString(#[serde(with = "super")] BString);
#[test]
fn end_to_end() {
let v = "45825ca36ef7628727b83f8a409a08ad";
let zeroes = [0u8; 32];
for i in 0..=32 {
let expected = MyString(v[i..].into());
let buf = serde_wormhole::to_vec(&expected).unwrap();
assert_eq!(&zeroes[..i], &buf[..i]);
assert_eq!(v.as_bytes()[i..], buf[i..]);
let actual = serde_wormhole::from_slice(&buf).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn value_too_large() {
let v = MyString("61ddb8ede2a3f0cec9b550ef150c08096280d0480493365f".into());
let _ = serde_wormhole::to_vec(&v)
.expect_err("successfully serialized string longer than 32 bytes");
}
#[test]
fn buffer_too_small() {
let b = [0u8; 16];
let _ = serde_wormhole::from_slice::<MyString>(&b)
.expect_err("successfully deserialized string from a buffer that's too small");
}
}

View File

@ -1,41 +1,224 @@
//! Exposes an API implementation depending on which feature flags have been toggled for the
//! library. Check submodules for chain runtime specific documentation.
use std::convert::TryFrom; // Remove in 2021
//! Provide Types and Data about Wormhole's supported chains.
/// Chain contains a mapping of Wormhole supported chains to their u16 representation. These are
/// universally defined among all Wormhole contracts.
#[repr(u16)]
#[derive(Clone, Debug, PartialEq, Eq)]
use std::{fmt, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Chain {
All = 0,
Solana = 1,
Ethereum = 2,
Terra = 3,
Binance = 4,
Polygon = 5,
AVAX = 6,
Oasis = 7,
/// In the wormhole wire format, 0 indicates that a message is for any destination chain, it is
/// represented here as `Any`.
Any,
/// Chains
Solana,
Ethereum,
Terra,
Bsc,
Polygon,
Avalanche,
Oasis,
Algorand,
Aurora,
Fantom,
Karura,
Acala,
Klaytn,
Celo,
Near,
Moonbeam,
Neon,
Terra2,
Injective,
Osmosis,
Sui,
Aptos,
Arbitrum,
Optimism,
Gnosis,
Pythnet,
Xpla,
Ropsten,
Wormchain,
// Allow arbitrary u16s to support future chains.
Unknown(u16),
}
impl TryFrom<u16> for Chain {
type Error = ();
fn try_from(other: u16) -> Result<Chain, Self::Error> {
impl From<u16> for Chain {
fn from(other: u16) -> Chain {
match other {
0 => Ok(Chain::All),
1 => Ok(Chain::Solana),
2 => Ok(Chain::Ethereum),
3 => Ok(Chain::Terra),
4 => Ok(Chain::Binance),
5 => Ok(Chain::Polygon),
6 => Ok(Chain::AVAX),
7 => Ok(Chain::Oasis),
_ => Err(()),
0 => Chain::Any,
1 => Chain::Solana,
2 => Chain::Ethereum,
3 => Chain::Terra,
4 => Chain::Bsc,
5 => Chain::Polygon,
6 => Chain::Avalanche,
7 => Chain::Oasis,
8 => Chain::Algorand,
9 => Chain::Aurora,
10 => Chain::Fantom,
11 => Chain::Karura,
12 => Chain::Acala,
13 => Chain::Klaytn,
14 => Chain::Celo,
15 => Chain::Near,
16 => Chain::Moonbeam,
17 => Chain::Neon,
18 => Chain::Terra2,
19 => Chain::Injective,
20 => Chain::Osmosis,
21 => Chain::Sui,
22 => Chain::Aptos,
23 => Chain::Arbitrum,
24 => Chain::Optimism,
25 => Chain::Gnosis,
26 => Chain::Pythnet,
28 => Chain::Xpla,
3104 => Chain::Wormchain,
10001 => Chain::Ropsten,
c => Chain::Unknown(c),
}
}
}
impl From<Chain> for u16 {
fn from(other: Chain) -> u16 {
match other {
Chain::Any => 0,
Chain::Solana => 1,
Chain::Ethereum => 2,
Chain::Terra => 3,
Chain::Bsc => 4,
Chain::Polygon => 5,
Chain::Avalanche => 6,
Chain::Oasis => 7,
Chain::Algorand => 8,
Chain::Aurora => 9,
Chain::Fantom => 10,
Chain::Karura => 11,
Chain::Acala => 12,
Chain::Klaytn => 13,
Chain::Celo => 14,
Chain::Near => 15,
Chain::Moonbeam => 16,
Chain::Neon => 17,
Chain::Terra2 => 18,
Chain::Injective => 19,
Chain::Osmosis => 20,
Chain::Sui => 21,
Chain::Aptos => 22,
Chain::Arbitrum => 23,
Chain::Optimism => 24,
Chain::Gnosis => 25,
Chain::Pythnet => 26,
Chain::Xpla => 28,
Chain::Wormchain => 3104,
Chain::Ropsten => 10001,
Chain::Unknown(c) => c,
}
}
}
impl fmt::Display for Chain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Any => f.write_str("Any"),
Self::Solana => f.write_str("Solana"),
Self::Ethereum => f.write_str("Ethereum"),
Self::Terra => f.write_str("Terra"),
Self::Bsc => f.write_str("Bsc"),
Self::Polygon => f.write_str("Polygon"),
Self::Avalanche => f.write_str("Avalanche"),
Self::Oasis => f.write_str("Oasis"),
Self::Algorand => f.write_str("Algorand"),
Self::Aurora => f.write_str("Aurora"),
Self::Fantom => f.write_str("Fantom"),
Self::Karura => f.write_str("Karura"),
Self::Acala => f.write_str("Acala"),
Self::Klaytn => f.write_str("Klaytn"),
Self::Celo => f.write_str("Celo"),
Self::Near => f.write_str("Near"),
Self::Moonbeam => f.write_str("Moonbeam"),
Self::Neon => f.write_str("Neon"),
Self::Terra2 => f.write_str("Terra2"),
Self::Injective => f.write_str("Injective"),
Self::Osmosis => f.write_str("Osmosis"),
Self::Sui => f.write_str("Sui"),
Self::Aptos => f.write_str("Aptos"),
Self::Arbitrum => f.write_str("Arbitrum"),
Self::Optimism => f.write_str("Optimism"),
Self::Gnosis => f.write_str("Gnosis"),
Self::Pythnet => f.write_str("Pythnet"),
Self::Xpla => f.write_str("Xpla"),
Self::Ropsten => f.write_str("Ropsten"),
Self::Wormchain => f.write_str("Wormchain"),
Self::Unknown(v) => write!(f, "Unknown({v})"),
}
}
}
impl FromStr for Chain {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Any" | "any" | "ANY" => Ok(Chain::Any),
"Solana" | "solana" | "SOLANA" => Ok(Chain::Solana),
"Ethereum" | "ethereum" | "ETHEREUM" => Ok(Chain::Ethereum),
"Terra" | "terra" | "TERRA" => Ok(Chain::Terra),
"Bsc" | "bsc" | "BSC" => Ok(Chain::Bsc),
"Polygon" | "polygon" | "POLYGON" => Ok(Chain::Polygon),
"Avalanche" | "avalanche" | "AVALANCHE" => Ok(Chain::Avalanche),
"Oasis" | "oasis" | "OASIS" => Ok(Chain::Oasis),
"Algorand" | "algorand" | "ALGORAND" => Ok(Chain::Algorand),
"Aurora" | "aurora" | "AURORA" => Ok(Chain::Aurora),
"Fantom" | "fantom" | "FANTOM" => Ok(Chain::Fantom),
"Karura" | "karura" | "KARURA" => Ok(Chain::Karura),
"Acala" | "acala" | "ACALA" => Ok(Chain::Acala),
"Klaytn" | "klaytn" | "KLAYTN" => Ok(Chain::Klaytn),
"Celo" | "celo" | "CELO" => Ok(Chain::Celo),
"Near" | "near" | "NEAR" => Ok(Chain::Near),
"Moonbeam" | "moonbeam" | "MOONBEAM" => Ok(Chain::Moonbeam),
"Neon" | "neon" | "NEON" => Ok(Chain::Neon),
"Terra2" | "terra2" | "TERRA2" => Ok(Chain::Terra2),
"Injective" | "injective" | "INJECTIVE" => Ok(Chain::Injective),
"Osmosis" | "osmosis" | "OSMOSIS" => Ok(Chain::Osmosis),
"Sui" | "sui" | "SUI" => Ok(Chain::Sui),
"Aptos" | "aptos" | "APTOS" => Ok(Chain::Aptos),
"Arbitrum" | "arbitrum" | "ARBITRUM" => Ok(Chain::Arbitrum),
"Optimism" | "optimism" | "OPTIMISM" => Ok(Chain::Optimism),
"Gnosis" | "gnosis" | "GNOSIS" => Ok(Chain::Gnosis),
"Pythnet" | "pythnet" | "PYTHNET" => Ok(Chain::Pythnet),
"Xpla" | "xpla" | "XPLA" => Ok(Chain::Xpla),
"Ropsten" | "ropsten" | "ROPSTEN" => Ok(Chain::Ropsten),
"Wormchain" | "wormchain" | "WORMCHAIN" => Ok(Chain::Wormchain),
_ => Err(format!("invalid chain: {s}")),
}
}
}
impl Default for Chain {
fn default() -> Self {
Self::All
Self::Any
}
}
impl Serialize for Chain {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u16((*self).into())
}
}
impl<'de> Deserialize<'de> for Chain {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
<u16 as Deserialize>::deserialize(deserializer).map(Self::from)
}
}

556
sdk/rust/core/src/core.rs Normal file
View File

@ -0,0 +1,556 @@
//! Parsers for core bridge VAAs.
//!
//! The core bridge is responsible for emitting messages and VAAs. But it also uses VAA's to
//! update and manage itself.
use serde::{Deserialize, Serialize};
use crate::{Address, Amount, Chain, GuardianSetInfo};
pub type Vaa = crate::Vaa<GovernancePacket>;
/// Represents a governance action targeted at the core bridge itself.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Action {
#[serde(rename = "1")]
ContractUpgrade { new_contract: Address },
#[serde(rename = "2")]
GuardianSetUpgrade {
new_guardian_set_index: u32,
new_guardian_set: GuardianSetInfo,
},
#[serde(rename = "3")]
SetFee { amount: Amount },
#[serde(rename = "4")]
TransferFee { amount: Amount, recipient: Address },
}
/// Represents the payload for a governance VAA targeted at the core bridge.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GovernancePacket {
/// Describes the chain on which the governance action should be carried out.
pub chain: Chain,
/// The actual governance action to be carried out.
pub action: Action,
}
// The wire format for GovernancePackets is wonky and doesn't lend itself well to auto-deriving
// Serialize / Deserialize so we implement it manually here.
mod governance_packet_impl {
use std::fmt;
use serde::{
de::{Error, MapAccess, SeqAccess, Unexpected, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
use crate::{
core::{Action, GovernancePacket},
Address, Amount, GuardianSetInfo,
};
// MODULE = "Core"
const MODULE: [u8; 32] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f,
0x72, 0x65,
];
struct Module;
impl Serialize for Module {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
MODULE.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Module {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let arr = <[u8; 32]>::deserialize(deserializer)?;
if arr == MODULE {
Ok(Module)
} else {
let expected = format!("{MODULE:?}");
Err(Error::invalid_value(Unexpected::Bytes(&arr), &&*expected))
}
}
}
#[derive(Serialize, Deserialize)]
struct ContractUpgrade {
new_contract: Address,
}
#[derive(Serialize, Deserialize)]
struct GuardianSetUpgrade {
new_guardian_set_index: u32,
new_guardian_set: GuardianSetInfo,
}
#[derive(Serialize, Deserialize)]
struct SetFee {
amount: Amount,
}
#[derive(Serialize, Deserialize)]
struct TransferFee {
amount: Amount,
recipient: Address,
}
impl Serialize for GovernancePacket {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_struct("GovernancePacket", 4)?;
seq.serialize_field("module", &Module)?;
// The wire format encodes the action before the chain and then appends the actual
// action payload.
match self.action {
Action::ContractUpgrade { new_contract } => {
seq.serialize_field("action", &1u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field("payload", &ContractUpgrade { new_contract })?;
}
Action::GuardianSetUpgrade {
new_guardian_set_index,
ref new_guardian_set,
} => {
seq.serialize_field("action", &2u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field(
"payload",
&GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set: new_guardian_set.clone(),
},
)?;
}
Action::SetFee { amount } => {
seq.serialize_field("action", &3u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field("payload", &SetFee { amount })?;
}
Action::TransferFee { amount, recipient } => {
seq.serialize_field("action", &4u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field("payload", &TransferFee { amount, recipient })?;
}
}
seq.end()
}
}
struct GovernancePacketVisitor;
impl<'de> Visitor<'de> for GovernancePacketVisitor {
type Value = GovernancePacket;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("struct GovernancePacket")
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
static EXPECTING: &str = "struct GovernancePacket with 4 elements";
let _: Module = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(0, &EXPECTING))?;
let act: u8 = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(1, &EXPECTING))?;
let chain = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(2, &EXPECTING))?;
let action = match act {
1 => {
let ContractUpgrade { new_contract } = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::ContractUpgrade { new_contract }
}
2 => {
let GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
} = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
}
}
3 => {
let SetFee { amount } = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::SetFee { amount }
}
4 => {
let TransferFee { amount, recipient } = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::TransferFee { amount, recipient }
}
v => {
return Err(Error::invalid_value(
Unexpected::Unsigned(v.into()),
&"one of 1, 2, 3, 4",
))
}
};
Ok(GovernancePacket { chain, action })
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Field {
Module,
Action,
Chain,
Payload,
}
let mut module = None;
let mut chain = None;
let mut action = None;
let mut payload = None;
while let Some(key) = map.next_key::<Field>()? {
match key {
Field::Module => {
if module.is_some() {
return Err(Error::duplicate_field("module"));
}
module = map.next_value::<Module>().map(Some)?;
}
Field::Action => {
if action.is_some() {
return Err(Error::duplicate_field("action"));
}
action = map.next_value::<u8>().map(Some)?;
}
Field::Chain => {
if chain.is_some() {
return Err(Error::duplicate_field("chain"));
}
chain = map.next_value().map(Some)?;
}
Field::Payload => {
if payload.is_some() {
return Err(Error::duplicate_field("payload"));
}
let a = action.as_ref().copied().ok_or_else(|| {
Error::custom("`action` must be known before deserializing `payload`")
})?;
let p = match a {
1 => {
let ContractUpgrade { new_contract } = map.next_value()?;
Action::ContractUpgrade { new_contract }
}
2 => {
let GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
} = map.next_value()?;
Action::GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
}
}
3 => {
let SetFee { amount } = map.next_value()?;
Action::SetFee { amount }
}
4 => {
let TransferFee { amount, recipient } = map.next_value()?;
Action::TransferFee { amount, recipient }
}
v => {
return Err(Error::custom(format_args!(
"invalid action: {v}, expected one of: 1, 2, 3, 4"
)))
}
};
payload = Some(p);
}
}
}
let chain = chain.ok_or_else(|| Error::missing_field("chain"))?;
let action = payload.ok_or_else(|| Error::missing_field("payload"))?;
Ok(GovernancePacket { chain, action })
}
}
impl<'de> Deserialize<'de> for GovernancePacket {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &[&str] = &["module", "action", "chain", "payload"];
deserializer.deserialize_struct("GovernancePacket", FIELDS, GovernancePacketVisitor)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{vaa::Signature, GuardianAddress, GOVERNANCE_EMITTER};
#[test]
fn contract_upgrade() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xe3, 0xdb, 0x30, 0x93, 0x03, 0xb7, 0x12,
0xa5, 0x62, 0xe6, 0xaa, 0x2a, 0xdc, 0x68, 0xbc, 0x10, 0xff, 0x22, 0x32, 0x8a, 0xb3,
0x1d, 0xdb, 0x6a, 0x83, 0x70, 0x69, 0x43, 0xa9, 0xda, 0x97, 0xbf, 0x11, 0xba, 0x6e,
0x3b, 0x96, 0x39, 0x55, 0x15, 0x86, 0x87, 0x86, 0x89, 0x8d, 0xc1, 0x9e, 0xcd, 0x73,
0x7d, 0x19, 0x7b, 0x0d, 0x1a, 0x1f, 0x3f, 0x3c, 0x6a, 0xea, 0xd5, 0xc1, 0xfe, 0x70,
0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0xc5, 0xd0, 0x5a, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x72,
0x65, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x46, 0xda, 0x7a, 0x03, 0x20, 0xdd, 0x99, 0x94, 0x38, 0xb4, 0x43,
0x5d, 0xac, 0x82, 0xbf, 0x1d, 0xac, 0x13, 0xd2,
];
let vaa = Vaa {
version: 1,
guardian_set_index: 0,
signatures: vec![Signature {
index: 0,
signature: [
0xe3, 0xdb, 0x30, 0x93, 0x03, 0xb7, 0x12, 0xa5, 0x62, 0xe6, 0xaa, 0x2a, 0xdc,
0x68, 0xbc, 0x10, 0xff, 0x22, 0x32, 0x8a, 0xb3, 0x1d, 0xdb, 0x6a, 0x83, 0x70,
0x69, 0x43, 0xa9, 0xda, 0x97, 0xbf, 0x11, 0xba, 0x6e, 0x3b, 0x96, 0x39, 0x55,
0x15, 0x86, 0x87, 0x86, 0x89, 0x8d, 0xc1, 0x9e, 0xcd, 0x73, 0x7d, 0x19, 0x7b,
0x0d, 0x1a, 0x1f, 0x3f, 0x3c, 0x6a, 0xea, 0xd5, 0xc1, 0xfe, 0x70, 0x09, 0x00,
],
}],
timestamp: 1,
nonce: 1,
emitter_chain: Chain::Solana,
emitter_address: GOVERNANCE_EMITTER,
sequence: 80_072_794,
consistency_level: 0,
payload: GovernancePacket {
chain: Chain::Fantom,
action: Action::ContractUpgrade {
new_contract: Address([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x46, 0xda, 0x7a, 0x03, 0x20, 0xdd, 0x99, 0x94, 0x38, 0xb4, 0x43,
0x5d, 0xac, 0x82, 0xbf, 0x1d, 0xac, 0x13, 0xd2,
]),
},
},
};
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
let encoded = serde_json::to_string(&vaa).unwrap();
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
}
#[test]
fn guardian_set_upgrade() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x7a, 0xc3, 0x1b, 0x28, 0x2c, 0x2a, 0xee,
0xeb, 0x37, 0xf3, 0x38, 0x5e, 0xe0, 0xde, 0x5f, 0x8e, 0x42, 0x1d, 0x30, 0xb9, 0xe5,
0xae, 0x8b, 0xa3, 0xd4, 0x37, 0x5c, 0x1c, 0x77, 0xa8, 0x6e, 0x77, 0x15, 0x9b, 0xb6,
0x97, 0xd9, 0xc4, 0x56, 0xd6, 0xf8, 0xc0, 0x2d, 0x22, 0xa9, 0x4b, 0x12, 0x79, 0xb6,
0x5b, 0x0d, 0x6a, 0x99, 0x57, 0xe7, 0xd3, 0x85, 0x74, 0x23, 0x84, 0x5a, 0xc7, 0x58,
0xe3, 0x00, 0x61, 0x0a, 0xc1, 0xd2, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x72,
0x65, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, 0x13, 0x58, 0xcc, 0x3a, 0xe5, 0xc0,
0x97, 0xb2, 0x13, 0xce, 0x3c, 0x81, 0x97, 0x9e, 0x1b, 0x9f, 0x95, 0x70, 0x74, 0x6a,
0xa5, 0xff, 0x6c, 0xb9, 0x52, 0x58, 0x9b, 0xde, 0x86, 0x2c, 0x25, 0xef, 0x43, 0x92,
0x13, 0x2f, 0xb9, 0xd4, 0xa4, 0x21, 0x57, 0x11, 0x4d, 0xe8, 0x46, 0x01, 0x93, 0xbd,
0xf3, 0xa2, 0xfc, 0xf8, 0x1f, 0x86, 0xa0, 0x97, 0x65, 0xf4, 0x76, 0x2f, 0xd1, 0x10,
0x7a, 0x00, 0x86, 0xb3, 0x2d, 0x7a, 0x09, 0x77, 0x92, 0x6a, 0x20, 0x51, 0x31, 0xd8,
0x73, 0x1d, 0x39, 0xcb, 0xeb, 0x8c, 0x82, 0xb2, 0xfd, 0x82, 0xfa, 0xed, 0x27, 0x11,
0xd5, 0x9a, 0xf0, 0xf2, 0x49, 0x9d, 0x16, 0xe7, 0x26, 0xf6, 0xb2, 0x11, 0xb3, 0x97,
0x56, 0xc0, 0x42, 0x44, 0x1b, 0xe6, 0xd8, 0x65, 0x0b, 0x69, 0xb5, 0x4e, 0xbe, 0x71,
0x5e, 0x23, 0x43, 0x54, 0xce, 0x5b, 0x4d, 0x34, 0x8f, 0xb7, 0x4b, 0x95, 0x8e, 0x89,
0x66, 0xe2, 0xec, 0x3d, 0xbd, 0x49, 0x58, 0xa7, 0xcd, 0xeb, 0x5f, 0x73, 0x89, 0xfa,
0x26, 0x94, 0x15, 0x19, 0xf0, 0x86, 0x33, 0x49, 0xc2, 0x23, 0xb7, 0x3a, 0x6d, 0xde,
0xe7, 0x74, 0xa3, 0xbf, 0x91, 0x39, 0x53, 0xd6, 0x95, 0x26, 0x0d, 0x88, 0xbc, 0x1a,
0xa2, 0x5a, 0x4e, 0xee, 0x36, 0x3e, 0xf0, 0x00, 0x0a, 0xc0, 0x07, 0x67, 0x27, 0xb3,
0x5f, 0xbe, 0xa2, 0xda, 0xc2, 0x8f, 0xee, 0x5c, 0xcb, 0x0f, 0xea, 0x76, 0x8e, 0xaf,
0x45, 0xce, 0xd1, 0x36, 0xb9, 0xd9, 0xe2, 0x49, 0x03, 0x46, 0x4a, 0xe8, 0x89, 0xf5,
0xc8, 0xa7, 0x23, 0xfc, 0x14, 0xf9, 0x31, 0x24, 0xb7, 0xc7, 0x38, 0x84, 0x3c, 0xbb,
0x89, 0xe8, 0x64, 0xc8, 0x62, 0xc3, 0x8c, 0xdd, 0xcc, 0xcf, 0x95, 0xd2, 0xcc, 0x37,
0xa4, 0xdc, 0x03, 0x6a, 0x8d, 0x23, 0x2b, 0x48, 0xf6, 0x2c, 0xdd, 0x47, 0x31, 0x41,
0x2f, 0x48, 0x90, 0xda, 0x79, 0x8f, 0x68, 0x96, 0xa3, 0x33, 0x1f, 0x64, 0xb4, 0x8c,
0x12, 0xd1, 0xd5, 0x7f, 0xd9, 0xcb, 0xe7, 0x08, 0x11, 0x71, 0xaa, 0x1b, 0xe1, 0xd3,
0x6c, 0xaf, 0xe3, 0x86, 0x79, 0x10, 0xf9, 0x9c, 0x09, 0xe3, 0x47, 0x89, 0x9c, 0x19,
0xc3, 0x81, 0x92, 0xb6, 0xe7, 0x38, 0x7c, 0xcd, 0x76, 0x82, 0x77, 0xc1, 0x7d, 0xab,
0x1b, 0x7a, 0x50, 0x27, 0xc0, 0xb3, 0xcf, 0x17, 0x8e, 0x21, 0xad, 0x2e, 0x77, 0xae,
0x06, 0x71, 0x15, 0x49, 0xcf, 0xbb, 0x1f, 0x9c, 0x7a, 0x9d, 0x80, 0x96, 0xe8, 0x5e,
0x14, 0x87, 0xf3, 0x55, 0x15, 0xd0, 0x2a, 0x92, 0x75, 0x35, 0x04, 0xa8, 0xd7, 0x54,
0x71, 0xb9, 0xf4, 0x9e, 0xdb, 0x6f, 0xbe, 0xbc, 0x89, 0x8f, 0x40, 0x3e, 0x47, 0x73,
0xe9, 0x5f, 0xeb, 0x15, 0xe8, 0x0c, 0x9a, 0x99, 0xc8, 0x34, 0x8d,
];
let vaa = Vaa {
version: 1,
guardian_set_index: 0,
signatures: vec![Signature {
index: 0,
signature: [
0x7a, 0xc3, 0x1b, 0x28, 0x2c, 0x2a, 0xee, 0xeb, 0x37, 0xf3, 0x38, 0x5e, 0xe0,
0xde, 0x5f, 0x8e, 0x42, 0x1d, 0x30, 0xb9, 0xe5, 0xae, 0x8b, 0xa3, 0xd4, 0x37,
0x5c, 0x1c, 0x77, 0xa8, 0x6e, 0x77, 0x15, 0x9b, 0xb6, 0x97, 0xd9, 0xc4, 0x56,
0xd6, 0xf8, 0xc0, 0x2d, 0x22, 0xa9, 0x4b, 0x12, 0x79, 0xb6, 0x5b, 0x0d, 0x6a,
0x99, 0x57, 0xe7, 0xd3, 0x85, 0x74, 0x23, 0x84, 0x5a, 0xc7, 0x58, 0xe3, 0x00,
],
}],
timestamp: 1_628_094_930,
nonce: 3,
emitter_chain: Chain::Solana,
emitter_address: GOVERNANCE_EMITTER,
sequence: 1337,
consistency_level: 0,
payload: GovernancePacket {
chain: Chain::Aptos,
action: Action::GuardianSetUpgrade {
new_guardian_set_index: 1,
new_guardian_set: GuardianSetInfo {
addresses: vec![
GuardianAddress([
0x58, 0xcc, 0x3a, 0xe5, 0xc0, 0x97, 0xb2, 0x13, 0xce, 0x3c, 0x81,
0x97, 0x9e, 0x1b, 0x9f, 0x95, 0x70, 0x74, 0x6a, 0xa5,
]),
GuardianAddress([
0xff, 0x6c, 0xb9, 0x52, 0x58, 0x9b, 0xde, 0x86, 0x2c, 0x25, 0xef,
0x43, 0x92, 0x13, 0x2f, 0xb9, 0xd4, 0xa4, 0x21, 0x57,
]),
GuardianAddress([
0x11, 0x4d, 0xe8, 0x46, 0x01, 0x93, 0xbd, 0xf3, 0xa2, 0xfc, 0xf8,
0x1f, 0x86, 0xa0, 0x97, 0x65, 0xf4, 0x76, 0x2f, 0xd1,
]),
GuardianAddress([
0x10, 0x7a, 0x00, 0x86, 0xb3, 0x2d, 0x7a, 0x09, 0x77, 0x92, 0x6a,
0x20, 0x51, 0x31, 0xd8, 0x73, 0x1d, 0x39, 0xcb, 0xeb,
]),
GuardianAddress([
0x8c, 0x82, 0xb2, 0xfd, 0x82, 0xfa, 0xed, 0x27, 0x11, 0xd5, 0x9a,
0xf0, 0xf2, 0x49, 0x9d, 0x16, 0xe7, 0x26, 0xf6, 0xb2,
]),
GuardianAddress([
0x11, 0xb3, 0x97, 0x56, 0xc0, 0x42, 0x44, 0x1b, 0xe6, 0xd8, 0x65,
0x0b, 0x69, 0xb5, 0x4e, 0xbe, 0x71, 0x5e, 0x23, 0x43,
]),
GuardianAddress([
0x54, 0xce, 0x5b, 0x4d, 0x34, 0x8f, 0xb7, 0x4b, 0x95, 0x8e, 0x89,
0x66, 0xe2, 0xec, 0x3d, 0xbd, 0x49, 0x58, 0xa7, 0xcd,
]),
GuardianAddress([
0xeb, 0x5f, 0x73, 0x89, 0xfa, 0x26, 0x94, 0x15, 0x19, 0xf0, 0x86,
0x33, 0x49, 0xc2, 0x23, 0xb7, 0x3a, 0x6d, 0xde, 0xe7,
]),
GuardianAddress([
0x74, 0xa3, 0xbf, 0x91, 0x39, 0x53, 0xd6, 0x95, 0x26, 0x0d, 0x88,
0xbc, 0x1a, 0xa2, 0x5a, 0x4e, 0xee, 0x36, 0x3e, 0xf0,
]),
GuardianAddress([
0x00, 0x0a, 0xc0, 0x07, 0x67, 0x27, 0xb3, 0x5f, 0xbe, 0xa2, 0xda,
0xc2, 0x8f, 0xee, 0x5c, 0xcb, 0x0f, 0xea, 0x76, 0x8e,
]),
GuardianAddress([
0xaf, 0x45, 0xce, 0xd1, 0x36, 0xb9, 0xd9, 0xe2, 0x49, 0x03, 0x46,
0x4a, 0xe8, 0x89, 0xf5, 0xc8, 0xa7, 0x23, 0xfc, 0x14,
]),
GuardianAddress([
0xf9, 0x31, 0x24, 0xb7, 0xc7, 0x38, 0x84, 0x3c, 0xbb, 0x89, 0xe8,
0x64, 0xc8, 0x62, 0xc3, 0x8c, 0xdd, 0xcc, 0xcf, 0x95,
]),
GuardianAddress([
0xd2, 0xcc, 0x37, 0xa4, 0xdc, 0x03, 0x6a, 0x8d, 0x23, 0x2b, 0x48,
0xf6, 0x2c, 0xdd, 0x47, 0x31, 0x41, 0x2f, 0x48, 0x90,
]),
GuardianAddress([
0xda, 0x79, 0x8f, 0x68, 0x96, 0xa3, 0x33, 0x1f, 0x64, 0xb4, 0x8c,
0x12, 0xd1, 0xd5, 0x7f, 0xd9, 0xcb, 0xe7, 0x08, 0x11,
]),
GuardianAddress([
0x71, 0xaa, 0x1b, 0xe1, 0xd3, 0x6c, 0xaf, 0xe3, 0x86, 0x79, 0x10,
0xf9, 0x9c, 0x09, 0xe3, 0x47, 0x89, 0x9c, 0x19, 0xc3,
]),
GuardianAddress([
0x81, 0x92, 0xb6, 0xe7, 0x38, 0x7c, 0xcd, 0x76, 0x82, 0x77, 0xc1,
0x7d, 0xab, 0x1b, 0x7a, 0x50, 0x27, 0xc0, 0xb3, 0xcf,
]),
GuardianAddress([
0x17, 0x8e, 0x21, 0xad, 0x2e, 0x77, 0xae, 0x06, 0x71, 0x15, 0x49,
0xcf, 0xbb, 0x1f, 0x9c, 0x7a, 0x9d, 0x80, 0x96, 0xe8,
]),
GuardianAddress([
0x5e, 0x14, 0x87, 0xf3, 0x55, 0x15, 0xd0, 0x2a, 0x92, 0x75, 0x35,
0x04, 0xa8, 0xd7, 0x54, 0x71, 0xb9, 0xf4, 0x9e, 0xdb,
]),
GuardianAddress([
0x6f, 0xbe, 0xbc, 0x89, 0x8f, 0x40, 0x3e, 0x47, 0x73, 0xe9, 0x5f,
0xeb, 0x15, 0xe8, 0x0c, 0x9a, 0x99, 0xc8, 0x34, 0x8d,
]),
],
expiration_time: 0,
},
},
},
};
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
let encoded = serde_json::to_string(&vaa).unwrap();
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
}
}

View File

@ -1,22 +0,0 @@
/// Ergonomic error handler for use within the Wormhole core/SDK libraries.
#[macro_export]
macro_rules! require {
($expr:expr, $name:ident) => {
if !$expr {
return Err($name.into());
}
};
}
/// This ErrorCode maps to the nom ParseError, we use an integer because the library is deprecating
/// the current error type, so we should avoid depending on it for now.
type ErrorCode = usize;
#[derive(Debug)]
pub enum WormholeError {
InvalidGovernanceAction,
InvalidGovernanceChain,
InvalidGovernanceModule,
DeserializeFailed,
ParseError(ErrorCode),
}

View File

@ -1,34 +1,71 @@
//! The `core` module provides all the pure Rust Wormhole primitives.
//!
//! This crate provides chain-agnostic types from Wormhole for consumption in on-chain contracts
//! and within other chain-specific Wormhole Rust SDK's. It includes:
//!
//! - Constants containing known network data/addresses.
//! - Parsers for VAA's and Payloads.
//! - Data types for Wormhole primitives such as GuardianSets and signatures.
//! - Verification Primitives for securely checking payloads.
#![deny(warnings)]
#![deny(unused_results)]
pub use chain::*;
pub use error::*;
pub use vaa::*;
use serde::{Deserialize, Serialize};
pub mod chain;
mod arraystring;
mod chain;
pub mod core;
pub mod nft;
mod serde_array;
pub mod token;
pub mod vaa;
#[macro_use]
pub mod error;
pub use {chain::Chain, vaa::Vaa};
/// Helper method that attempts to parse and truncate UTF-8 from a byte stream. This is useful when
/// the wire data is expected to contain UTF-8 that is either already truncated, or needs to be,
/// while still maintaining the ability to render.
///
/// This should be used to parse any Text-over-Wormhole fields that are meant to be human readable.
pub(crate) fn parse_fixed_utf8<T: AsRef<[u8]>, const N: usize>(s: T) -> Option<String> {
use bstr::ByteSlice;
use std::io::Cursor;
use std::io::Read;
/// The `GOVERNANCE_EMITTER` is a special address Wormhole guardians trust to observe governance
/// actions from. The value is "0000000000000000000000000000000000000000000000000000000000000004".
pub const GOVERNANCE_EMITTER: Address = Address([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
]);
// Read Bytes.
let mut cursor = Cursor::new(s.as_ref());
let mut buffer = vec![0u8; N];
cursor.read_exact(&mut buffer).ok()?;
buffer.retain(|&c| c != 0);
#[derive(
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
pub struct GuardianAddress(pub [u8; 20]);
// Attempt UTF-8 Decoding. Stripping invalid Unicode characters (0xFFFD).
let mut buffer: Vec<char> = buffer.chars().collect();
buffer.retain(|&c| c != '\u{FFFD}');
/// Wormhole specifies addresses as 32 bytes. Addresses that are shorter, for example 20 byte
/// Ethereum addresses, are left zero padded to 32.
#[derive(
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
pub struct Address(pub [u8; 32]);
Some(buffer.iter().collect())
/// Wormhole specifies an amount as a uint256 encoded in big-endian order.
#[derive(
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
pub struct Amount(pub [u8; 32]);
/// A `GuardianSet` is a versioned set of keys that can sign Wormhole messages.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GuardianSetInfo {
/// The set of guardians public keys, in Ethereum's compressed format.
pub addresses: Vec<GuardianAddress>,
/// How long after a GuardianSet change before this set is expired.
#[serde(skip)]
pub expiration_time: u64,
}
impl GuardianSetInfo {
pub fn quorum(&self) -> usize {
// allow quorum of 0 for testing purposes...
if self.addresses.is_empty() {
0
} else {
((self.addresses.len() * 10 / 3) * 2) / 10 + 1
}
}
}

701
sdk/rust/core/src/nft.rs Normal file
View File

@ -0,0 +1,701 @@
//! Parsers for NFT bridge VAA Actions..
//!
//! NFT bridging relies on VAA's that indicate custody/lockup/burn events in order to maintain
//! token parity between multiple chains. Parsers are provided here that can be used to read and
//! verify these events.
use bstr::BString;
use serde::{Deserialize, Serialize};
use crate::{Address, Chain};
/// The ID of the token, encoded as a big-endian uint256.
#[derive(
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
pub struct TokenId(pub [u8; 32]);
/// Represents a non-governance action targeted at the NFT bridge.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Message {
/// The Transfer message contains specifics detailing a token lock up on a sending chain. Chains
/// that are attempting to initiate a transfer must lock up tokens in some manner, such as in a
/// custody account or via burning, before emitting this message.
#[serde(rename = "1")]
Transfer {
/// Address of the token. Left-zero-padded if shorter than 32 bytes
nft_address: Address,
/// Chain ID of the token
nft_chain: Chain,
/// Symbol of the token
#[serde(with = "crate::arraystring")]
symbol: BString,
/// Name of the token
#[serde(with = "crate::arraystring")]
name: BString,
/// TokenID of the token
token_id: TokenId,
/// URI of the token metadata
uri: BString,
/// The address of the recipient.
to: Address,
/// The chain ID of the recipient.
to_chain: Chain,
},
}
/// Represents a governance action targeted at the NFT bridge.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Action {
/// Registers an emitter address for a particular chain on a different chain. An emitter
/// address must be registered for a chain and must match the emitter address in the VAA before
/// the NFT bridge will accept VAAs from that chain.
#[serde(rename = "1")]
RegisterChain {
chain: Chain,
emitter_address: Address,
},
/// Upgrades the NFT bridge contract to a new address.
#[serde(rename = "2")]
ContractUpgrade { new_contract: Address },
}
/// Represents the payload for a governance VAA targeted at the NFT bridge.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GovernancePacket {
/// The chain on which the governance action should be carried out.
pub chain: Chain,
/// The actual governance action to be carried out.
pub action: Action,
}
// The wire format for GovernancePackets is wonky and doesn't lend itself well to auto-deriving
// Serialize / Deserialize so we implement it manually here.
mod governance_packet_impl {
use std::fmt;
use serde::{
de::{Error, MapAccess, SeqAccess, Unexpected, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
use crate::{
nft::{Action, GovernancePacket},
Address, Chain,
};
// MODULE = "NFTBridge"
const MODULE: [u8; 32] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x46, 0x54, 0x42, 0x72, 0x69, 0x64,
0x67, 0x65,
];
struct Module;
impl Serialize for Module {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
MODULE.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Module {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let arr = <[u8; 32]>::deserialize(deserializer)?;
if arr == MODULE {
Ok(Module)
} else {
let expected = format!("{MODULE:?}");
Err(Error::invalid_value(Unexpected::Bytes(&arr), &&*expected))
}
}
}
#[derive(Serialize, Deserialize)]
struct ContractUpgrade {
new_contract: Address,
}
#[derive(Serialize, Deserialize)]
struct RegisterChain {
chain: Chain,
emitter_address: Address,
}
impl Serialize for GovernancePacket {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_struct("GovernancePacket", 4)?;
seq.serialize_field("module", &Module)?;
// The wire format encodes the action before the chain and then appends the actual
// action payload.
match self.action {
Action::RegisterChain {
chain,
emitter_address,
} => {
seq.serialize_field("action", &1u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field(
"payload",
&RegisterChain {
chain,
emitter_address,
},
)?;
}
Action::ContractUpgrade { new_contract } => {
seq.serialize_field("action", &2u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field("payload", &ContractUpgrade { new_contract })?;
}
}
seq.end()
}
}
struct GovernancePacketVisitor;
impl<'de> Visitor<'de> for GovernancePacketVisitor {
type Value = GovernancePacket;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("struct GovernancePacket")
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
static EXPECTING: &str = "struct GovernancePacket with 4 elements";
let _: Module = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(0, &EXPECTING))?;
let act: u8 = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(1, &EXPECTING))?;
let chain = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(2, &EXPECTING))?;
let action = match act {
1 => {
let RegisterChain {
chain,
emitter_address,
} = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::RegisterChain {
chain,
emitter_address,
}
}
2 => {
let ContractUpgrade { new_contract } = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::ContractUpgrade { new_contract }
}
v => {
return Err(Error::invalid_value(
Unexpected::Unsigned(v.into()),
&"one of 1, 2",
))
}
};
Ok(GovernancePacket { chain, action })
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Field {
Module,
Action,
Chain,
Payload,
}
let mut module = None;
let mut chain = None;
let mut action = None;
let mut payload = None;
while let Some(key) = map.next_key::<Field>()? {
match key {
Field::Module => {
if module.is_some() {
return Err(Error::duplicate_field("module"));
}
module = map.next_value::<Module>().map(Some)?;
}
Field::Action => {
if action.is_some() {
return Err(Error::duplicate_field("action"));
}
action = map.next_value::<u8>().map(Some)?;
}
Field::Chain => {
if chain.is_some() {
return Err(Error::duplicate_field("chain"));
}
chain = map.next_value().map(Some)?;
}
Field::Payload => {
if payload.is_some() {
return Err(Error::duplicate_field("payload"));
}
let a = action.as_ref().copied().ok_or_else(|| {
Error::custom("`action` must be known before deserializing `payload`")
})?;
let p = match a {
1 => {
let RegisterChain {
chain,
emitter_address,
} = map.next_value()?;
Action::RegisterChain {
chain,
emitter_address,
}
}
2 => {
let ContractUpgrade { new_contract } = map.next_value()?;
Action::ContractUpgrade { new_contract }
}
v => {
return Err(Error::custom(format_args!(
"invalid action: {v}, expected one of: 1, 2"
)))
}
};
payload = Some(p);
}
}
}
let _ = module.ok_or_else(|| Error::missing_field("module"))?;
let chain = chain.ok_or_else(|| Error::missing_field("chain"))?;
let action = payload.ok_or_else(|| Error::missing_field("payload"))?;
Ok(GovernancePacket { chain, action })
}
}
impl<'de> Deserialize<'de> for GovernancePacket {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &[&str] = &["module", "action", "chain", "payload"];
deserializer.deserialize_struct("GovernancePacket", FIELDS, GovernancePacketVisitor)
}
}
}
#[cfg(test)]
mod test {
use crate::{vaa::Signature, Vaa, GOVERNANCE_EMITTER};
use super::*;
#[test]
fn transfer() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x77, 0xbb, 0x0b,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x4f, 0x4f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42,
0x41, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0a,
];
let vaa = Vaa {
version: 1,
guardian_set_index: 0,
signatures: vec![],
timestamp: 1,
nonce: 1,
emitter_chain: Chain::Solana,
emitter_address: GOVERNANCE_EMITTER,
sequence: 41_401_099,
consistency_level: 0,
payload: Message::Transfer {
nft_address: GOVERNANCE_EMITTER,
nft_chain: Chain::Solana,
symbol: "FOO".into(),
name: "BAR".into(),
token_id: TokenId([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
]),
uri: "google.com".into(),
to: GOVERNANCE_EMITTER,
to_chain: Chain::Fantom,
},
};
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
let encoded = serde_json::to_string(&vaa).unwrap();
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
}
#[test]
fn register_chain() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c,
0x1d, 0x08, 0x90, 0x5c, 0x02, 0xe2, 0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6,
0xc8, 0xdb, 0x4f, 0x77, 0xfe, 0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e,
0x92, 0xa9, 0x19, 0x4d, 0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6,
0xf0, 0xc2, 0x1c, 0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e,
0x98, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3c, 0x1b, 0xfa, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x46, 0x54, 0x42, 0x72, 0x69, 0x64, 0x67,
0x65, 0x01, 0x00, 0x00, 0x00, 0x01, 0x3b, 0x26, 0x40, 0x9f, 0x8a, 0xad, 0xed, 0x3f,
0x5d, 0xdc, 0xa1, 0x84, 0x69, 0x5a, 0xa6, 0xa0, 0xfa, 0x82, 0x9b, 0x0c, 0x85, 0xca,
0xf8, 0x48, 0x56, 0x32, 0x48, 0x96, 0xd2, 0x14, 0xca, 0x98,
];
let vaa = Vaa {
version: 1,
guardian_set_index: 0,
signatures: vec![Signature {
index: 0,
signature: [
0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d, 0x08, 0x90, 0x5c, 0x02, 0xe2,
0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb, 0x4f, 0x77, 0xfe,
0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9, 0x19, 0x4d,
0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2, 0x1c,
0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01,
],
}],
timestamp: 1,
nonce: 1,
emitter_chain: Chain::Solana,
emitter_address: GOVERNANCE_EMITTER,
sequence: 20_716_538,
consistency_level: 0,
payload: GovernancePacket {
chain: Chain::Any,
action: Action::RegisterChain {
chain: Chain::Solana,
emitter_address: Address([
0x3b, 0x26, 0x40, 0x9f, 0x8a, 0xad, 0xed, 0x3f, 0x5d, 0xdc, 0xa1, 0x84,
0x69, 0x5a, 0xa6, 0xa0, 0xfa, 0x82, 0x9b, 0x0c, 0x85, 0xca, 0xf8, 0x48,
0x56, 0x32, 0x48, 0x96, 0xd2, 0x14, 0xca, 0x98,
]),
},
},
};
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
let encoded = serde_json::to_string(&vaa).unwrap();
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
}
#[test]
fn contract_upgrade() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x73, 0xaf, 0x3c, 0x2f, 0x55, 0x98, 0x90,
0x41, 0xb0, 0x06, 0x8a, 0x4e, 0xae, 0xba, 0x8f, 0x88, 0x25, 0x10, 0x60, 0x21, 0x99,
0x6d, 0xe6, 0x14, 0x39, 0x54, 0xc0, 0x6f, 0xc4, 0xcf, 0xad, 0x5c, 0x3f, 0x1a, 0xba,
0x6c, 0xa9, 0x51, 0x18, 0x8e, 0x96, 0x2f, 0x11, 0x25, 0x1c, 0x25, 0xca, 0x98, 0x62,
0x86, 0x85, 0xe2, 0xfc, 0x2b, 0x65, 0xec, 0x60, 0x94, 0x50, 0xcf, 0xc4, 0xe6, 0x51,
0x12, 0x00, 0x03, 0x00, 0xc6, 0x2e, 0xb0, 0xde, 0x7a, 0xb3, 0xd0, 0x28, 0x92, 0x0f,
0x18, 0xe1, 0x0d, 0xf7, 0xf2, 0xa0, 0x4d, 0x00, 0xeb, 0x88, 0xb8, 0x94, 0x0a, 0x27,
0xbb, 0xce, 0x8c, 0xe6, 0x6c, 0x67, 0x52, 0x68, 0x82, 0xc9, 0x6c, 0xc5, 0x0d, 0xd3,
0x81, 0xbd, 0xbf, 0xf3, 0x0b, 0xb6, 0x5b, 0x93, 0x47, 0xca, 0xdf, 0xf0, 0x96, 0x76,
0x72, 0x90, 0x43, 0xaf, 0x60, 0xd6, 0x52, 0xa0, 0xb9, 0x06, 0x30, 0x00, 0x04, 0xf0,
0x7b, 0x0f, 0xe6, 0xca, 0xb1, 0x8b, 0xe7, 0xae, 0x82, 0x7f, 0xe1, 0x1c, 0xa8, 0x99,
0x2f, 0xf0, 0xb5, 0xa2, 0xa3, 0x2d, 0x65, 0xbe, 0x95, 0x82, 0x31, 0xbf, 0x84, 0xcf,
0x6b, 0x14, 0xfb, 0x5e, 0xeb, 0x8a, 0x51, 0xad, 0xe1, 0xc7, 0x0a, 0xa5, 0xb8, 0xb8,
0xcf, 0x83, 0xaf, 0xe3, 0xc2, 0x2d, 0x34, 0x71, 0x48, 0x1b, 0xda, 0x38, 0x96, 0xea,
0x2a, 0x70, 0x46, 0x9f, 0x96, 0x92, 0xb8, 0x00, 0x05, 0x74, 0xd4, 0x96, 0x72, 0x9d,
0xde, 0x6e, 0x19, 0x09, 0x59, 0xa1, 0x79, 0x45, 0xe8, 0x7c, 0xd9, 0x5e, 0x23, 0xc1,
0x00, 0x47, 0xab, 0x2e, 0xef, 0x5c, 0x7d, 0x0e, 0xc6, 0xf9, 0x12, 0x08, 0xc7, 0x27,
0x9d, 0x8a, 0x1b, 0x1f, 0x4a, 0x60, 0x06, 0x9d, 0x25, 0x93, 0x2a, 0x67, 0xd6, 0x55,
0xf2, 0xd0, 0xb4, 0x20, 0x59, 0x00, 0x3d, 0xe7, 0x5f, 0xd3, 0xca, 0x79, 0x92, 0xfd,
0xd1, 0xb0, 0xd4, 0x01, 0x06, 0x51, 0xf0, 0x6a, 0xb6, 0xfd, 0xfd, 0x42, 0xf3, 0x23,
0x84, 0x0a, 0x81, 0x8b, 0x38, 0xcf, 0xd1, 0xc9, 0x9d, 0x22, 0x36, 0x6c, 0x87, 0x79,
0x03, 0xb9, 0x8d, 0xf4, 0x0b, 0xda, 0xf2, 0xa2, 0x04, 0x2e, 0xb8, 0xcd, 0x0e, 0x59,
0xc4, 0x63, 0x7e, 0x6a, 0x80, 0xa1, 0x99, 0xb1, 0x10, 0x8c, 0xd7, 0x65, 0x9b, 0xa8,
0x37, 0x0f, 0x6f, 0x94, 0x76, 0xb4, 0x79, 0x83, 0x98, 0x54, 0xd8, 0xc1, 0xa6, 0x00,
0x07, 0x78, 0x4b, 0x37, 0xc6, 0x10, 0x3c, 0x75, 0x2c, 0xd9, 0x7a, 0x58, 0x6b, 0xe8,
0xe1, 0xce, 0xff, 0x22, 0xa6, 0xe4, 0x88, 0x43, 0x32, 0x84, 0xff, 0x31, 0xcd, 0x90,
0x8b, 0x5c, 0x7d, 0x87, 0xe2, 0x44, 0xda, 0x75, 0x16, 0xd0, 0x7a, 0x0f, 0xa6, 0x92,
0x68, 0x4f, 0x82, 0x6e, 0x5e, 0x6c, 0x8b, 0x45, 0x20, 0x04, 0x20, 0x6d, 0x6f, 0xe5,
0xba, 0xe4, 0x6a, 0xfb, 0x1c, 0x21, 0x8c, 0xf5, 0x0d, 0x00, 0x09, 0x0b, 0x6c, 0xcc,
0xe6, 0x7e, 0xd5, 0x01, 0xcb, 0x20, 0xfb, 0x06, 0xde, 0x76, 0xb9, 0x08, 0x3f, 0x13,
0x29, 0xad, 0x78, 0xd3, 0x84, 0x51, 0x7a, 0x94, 0xab, 0x8e, 0x9a, 0xa6, 0x01, 0xbe,
0x7a, 0x0f, 0x00, 0xbb, 0x60, 0x3b, 0x36, 0x50, 0x4a, 0x74, 0xdd, 0xc3, 0x67, 0x35,
0x9c, 0x8e, 0xa0, 0x7f, 0x5c, 0xcd, 0x50, 0x22, 0x77, 0x2c, 0xc1, 0x57, 0xe0, 0x0f,
0x54, 0x15, 0x63, 0x71, 0xe4, 0x01, 0x0b, 0xe1, 0xe6, 0xb7, 0x12, 0xa2, 0x35, 0x3e,
0x49, 0x07, 0x92, 0x9c, 0x40, 0x57, 0xd5, 0xba, 0xbe, 0xf8, 0x0d, 0xc0, 0x9f, 0xeb,
0xb3, 0xdc, 0x29, 0x34, 0xf4, 0x40, 0x48, 0x79, 0x5e, 0xef, 0x95, 0x4e, 0x99, 0x10,
0x6c, 0xb2, 0x9b, 0x1a, 0x8d, 0x93, 0x53, 0x69, 0xda, 0xec, 0xbb, 0x8b, 0xae, 0x45,
0x19, 0x2e, 0xfc, 0x3c, 0xed, 0xbc, 0x57, 0x31, 0x5d, 0x67, 0xec, 0x9e, 0xa8, 0x00,
0x0a, 0x00, 0x0d, 0xdc, 0xc0, 0x4c, 0xd7, 0x55, 0x6a, 0x94, 0xe8, 0xb4, 0x18, 0xdc,
0x3a, 0x8a, 0x00, 0xef, 0xb1, 0x28, 0xd4, 0xf7, 0x6f, 0x71, 0x43, 0x12, 0x47, 0xa9,
0x2e, 0x7a, 0x19, 0xa8, 0x33, 0xfc, 0x02, 0x21, 0x5d, 0x1f, 0xd6, 0x9e, 0x0e, 0x59,
0x6d, 0xa5, 0x31, 0xea, 0x58, 0x7a, 0xe2, 0xac, 0x2f, 0x36, 0xd2, 0x0f, 0x3b, 0x19,
0x33, 0x06, 0x0a, 0xd6, 0xef, 0x3e, 0x9d, 0x7d, 0x84, 0x75, 0xba, 0x01, 0x0e, 0xc7,
0x62, 0x16, 0x49, 0x49, 0x06, 0xae, 0xd8, 0xd8, 0x31, 0x0c, 0x31, 0xa5, 0xe4, 0xa5,
0x45, 0xea, 0x2a, 0xf8, 0xdb, 0xe3, 0x8e, 0xeb, 0x74, 0xa3, 0xd1, 0x4f, 0x07, 0x34,
0xdd, 0x2b, 0x1a, 0x21, 0xb9, 0x85, 0x0e, 0xa2, 0x0e, 0x6c, 0x2c, 0xa4, 0x22, 0x48,
0x78, 0x8b, 0xfb, 0x17, 0x43, 0x9d, 0x37, 0xab, 0xda, 0x25, 0xd7, 0x05, 0xb5, 0x68,
0xb2, 0x65, 0xb1, 0x13, 0xb9, 0x76, 0xc5, 0x00, 0x10, 0xc6, 0x1f, 0xe6, 0xeb, 0xea,
0x61, 0xf2, 0xca, 0xe8, 0x95, 0xc3, 0x34, 0x96, 0x50, 0x70, 0x48, 0x7d, 0x39, 0xab,
0x6b, 0xf0, 0x44, 0x28, 0x34, 0x0a, 0xf0, 0xf7, 0x69, 0x9f, 0xdd, 0xd3, 0xc0, 0x1e,
0x0c, 0x86, 0xda, 0xea, 0x9e, 0xb4, 0x49, 0x70, 0x12, 0x48, 0xa2, 0x4f, 0xa4, 0xc0,
0xff, 0x75, 0xfb, 0x60, 0x0b, 0x2c, 0x01, 0x34, 0xbd, 0x72, 0x17, 0x91, 0xf6, 0x67,
0x11, 0x6e, 0x42, 0x00, 0x11, 0x19, 0xb7, 0x9c, 0xaa, 0x25, 0x0e, 0x75, 0xda, 0x14,
0x82, 0xc7, 0xf4, 0x12, 0x68, 0x73, 0x08, 0xd1, 0x3e, 0xf7, 0x00, 0xf7, 0x26, 0xb9,
0x94, 0x17, 0xbb, 0x75, 0xae, 0x6d, 0xd1, 0x43, 0xfc, 0x61, 0x9f, 0x8c, 0x9c, 0x29,
0xc7, 0x3f, 0x99, 0x49, 0xaf, 0xfd, 0x16, 0x29, 0xcc, 0x28, 0x6a, 0x61, 0x91, 0x7e,
0xd7, 0x85, 0x37, 0x54, 0xa4, 0x67, 0x02, 0x92, 0x0b, 0x76, 0xcf, 0x90, 0xeb, 0x00,
0x12, 0xb0, 0xba, 0xb5, 0xe1, 0xa8, 0xb3, 0x21, 0xe9, 0x2f, 0x9d, 0x3d, 0xf9, 0x24,
0x30, 0x18, 0x3e, 0x48, 0x43, 0xe5, 0x7c, 0x6f, 0x8c, 0x15, 0xc2, 0x2d, 0x62, 0xb2,
0xf8, 0xe4, 0xb8, 0xcd, 0xc2, 0x75, 0x9f, 0x28, 0x54, 0xb8, 0xd1, 0x22, 0xac, 0xdb,
0x4b, 0x4d, 0x89, 0x28, 0xb8, 0x7d, 0xf4, 0x19, 0x9c, 0xc6, 0x83, 0x01, 0x1d, 0x2e,
0x9b, 0x7d, 0x41, 0xa9, 0x6d, 0x8b, 0x48, 0x0c, 0xcc, 0x01, 0x00, 0x00, 0x00, 0x00,
0x39, 0xc4, 0xba, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x25, 0x64, 0xd2, 0xef,
0xd6, 0x08, 0x4d, 0xf0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x4e, 0x46, 0x54, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x02, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x09, 0x83,
];
let vaa = Vaa {
version: 1,
guardian_set_index: 1,
signatures: vec![
Signature {
index: 2,
signature: [
0x73, 0xaf, 0x3c, 0x2f, 0x55, 0x98, 0x90, 0x41, 0xb0, 0x06, 0x8a, 0x4e,
0xae, 0xba, 0x8f, 0x88, 0x25, 0x10, 0x60, 0x21, 0x99, 0x6d, 0xe6, 0x14,
0x39, 0x54, 0xc0, 0x6f, 0xc4, 0xcf, 0xad, 0x5c, 0x3f, 0x1a, 0xba, 0x6c,
0xa9, 0x51, 0x18, 0x8e, 0x96, 0x2f, 0x11, 0x25, 0x1c, 0x25, 0xca, 0x98,
0x62, 0x86, 0x85, 0xe2, 0xfc, 0x2b, 0x65, 0xec, 0x60, 0x94, 0x50, 0xcf,
0xc4, 0xe6, 0x51, 0x12, 0x00,
],
},
Signature {
index: 3,
signature: [
0x00, 0xc6, 0x2e, 0xb0, 0xde, 0x7a, 0xb3, 0xd0, 0x28, 0x92, 0x0f, 0x18,
0xe1, 0x0d, 0xf7, 0xf2, 0xa0, 0x4d, 0x00, 0xeb, 0x88, 0xb8, 0x94, 0x0a,
0x27, 0xbb, 0xce, 0x8c, 0xe6, 0x6c, 0x67, 0x52, 0x68, 0x82, 0xc9, 0x6c,
0xc5, 0x0d, 0xd3, 0x81, 0xbd, 0xbf, 0xf3, 0x0b, 0xb6, 0x5b, 0x93, 0x47,
0xca, 0xdf, 0xf0, 0x96, 0x76, 0x72, 0x90, 0x43, 0xaf, 0x60, 0xd6, 0x52,
0xa0, 0xb9, 0x06, 0x30, 0x00,
],
},
Signature {
index: 4,
signature: [
0xf0, 0x7b, 0x0f, 0xe6, 0xca, 0xb1, 0x8b, 0xe7, 0xae, 0x82, 0x7f, 0xe1,
0x1c, 0xa8, 0x99, 0x2f, 0xf0, 0xb5, 0xa2, 0xa3, 0x2d, 0x65, 0xbe, 0x95,
0x82, 0x31, 0xbf, 0x84, 0xcf, 0x6b, 0x14, 0xfb, 0x5e, 0xeb, 0x8a, 0x51,
0xad, 0xe1, 0xc7, 0x0a, 0xa5, 0xb8, 0xb8, 0xcf, 0x83, 0xaf, 0xe3, 0xc2,
0x2d, 0x34, 0x71, 0x48, 0x1b, 0xda, 0x38, 0x96, 0xea, 0x2a, 0x70, 0x46,
0x9f, 0x96, 0x92, 0xb8, 0x00,
],
},
Signature {
index: 5,
signature: [
0x74, 0xd4, 0x96, 0x72, 0x9d, 0xde, 0x6e, 0x19, 0x09, 0x59, 0xa1, 0x79,
0x45, 0xe8, 0x7c, 0xd9, 0x5e, 0x23, 0xc1, 0x00, 0x47, 0xab, 0x2e, 0xef,
0x5c, 0x7d, 0x0e, 0xc6, 0xf9, 0x12, 0x08, 0xc7, 0x27, 0x9d, 0x8a, 0x1b,
0x1f, 0x4a, 0x60, 0x06, 0x9d, 0x25, 0x93, 0x2a, 0x67, 0xd6, 0x55, 0xf2,
0xd0, 0xb4, 0x20, 0x59, 0x00, 0x3d, 0xe7, 0x5f, 0xd3, 0xca, 0x79, 0x92,
0xfd, 0xd1, 0xb0, 0xd4, 0x01,
],
},
Signature {
index: 6,
signature: [
0x51, 0xf0, 0x6a, 0xb6, 0xfd, 0xfd, 0x42, 0xf3, 0x23, 0x84, 0x0a, 0x81,
0x8b, 0x38, 0xcf, 0xd1, 0xc9, 0x9d, 0x22, 0x36, 0x6c, 0x87, 0x79, 0x03,
0xb9, 0x8d, 0xf4, 0x0b, 0xda, 0xf2, 0xa2, 0x04, 0x2e, 0xb8, 0xcd, 0x0e,
0x59, 0xc4, 0x63, 0x7e, 0x6a, 0x80, 0xa1, 0x99, 0xb1, 0x10, 0x8c, 0xd7,
0x65, 0x9b, 0xa8, 0x37, 0x0f, 0x6f, 0x94, 0x76, 0xb4, 0x79, 0x83, 0x98,
0x54, 0xd8, 0xc1, 0xa6, 0x00,
],
},
Signature {
index: 7,
signature: [
0x78, 0x4b, 0x37, 0xc6, 0x10, 0x3c, 0x75, 0x2c, 0xd9, 0x7a, 0x58, 0x6b,
0xe8, 0xe1, 0xce, 0xff, 0x22, 0xa6, 0xe4, 0x88, 0x43, 0x32, 0x84, 0xff,
0x31, 0xcd, 0x90, 0x8b, 0x5c, 0x7d, 0x87, 0xe2, 0x44, 0xda, 0x75, 0x16,
0xd0, 0x7a, 0x0f, 0xa6, 0x92, 0x68, 0x4f, 0x82, 0x6e, 0x5e, 0x6c, 0x8b,
0x45, 0x20, 0x04, 0x20, 0x6d, 0x6f, 0xe5, 0xba, 0xe4, 0x6a, 0xfb, 0x1c,
0x21, 0x8c, 0xf5, 0x0d, 0x00,
],
},
Signature {
index: 9,
signature: [
0x0b, 0x6c, 0xcc, 0xe6, 0x7e, 0xd5, 0x01, 0xcb, 0x20, 0xfb, 0x06, 0xde,
0x76, 0xb9, 0x08, 0x3f, 0x13, 0x29, 0xad, 0x78, 0xd3, 0x84, 0x51, 0x7a,
0x94, 0xab, 0x8e, 0x9a, 0xa6, 0x01, 0xbe, 0x7a, 0x0f, 0x00, 0xbb, 0x60,
0x3b, 0x36, 0x50, 0x4a, 0x74, 0xdd, 0xc3, 0x67, 0x35, 0x9c, 0x8e, 0xa0,
0x7f, 0x5c, 0xcd, 0x50, 0x22, 0x77, 0x2c, 0xc1, 0x57, 0xe0, 0x0f, 0x54,
0x15, 0x63, 0x71, 0xe4, 0x01,
],
},
Signature {
index: 11,
signature: [
0xe1, 0xe6, 0xb7, 0x12, 0xa2, 0x35, 0x3e, 0x49, 0x07, 0x92, 0x9c, 0x40,
0x57, 0xd5, 0xba, 0xbe, 0xf8, 0x0d, 0xc0, 0x9f, 0xeb, 0xb3, 0xdc, 0x29,
0x34, 0xf4, 0x40, 0x48, 0x79, 0x5e, 0xef, 0x95, 0x4e, 0x99, 0x10, 0x6c,
0xb2, 0x9b, 0x1a, 0x8d, 0x93, 0x53, 0x69, 0xda, 0xec, 0xbb, 0x8b, 0xae,
0x45, 0x19, 0x2e, 0xfc, 0x3c, 0xed, 0xbc, 0x57, 0x31, 0x5d, 0x67, 0xec,
0x9e, 0xa8, 0x00, 0x0a, 0x00,
],
},
Signature {
index: 13,
signature: [
0xdc, 0xc0, 0x4c, 0xd7, 0x55, 0x6a, 0x94, 0xe8, 0xb4, 0x18, 0xdc, 0x3a,
0x8a, 0x00, 0xef, 0xb1, 0x28, 0xd4, 0xf7, 0x6f, 0x71, 0x43, 0x12, 0x47,
0xa9, 0x2e, 0x7a, 0x19, 0xa8, 0x33, 0xfc, 0x02, 0x21, 0x5d, 0x1f, 0xd6,
0x9e, 0x0e, 0x59, 0x6d, 0xa5, 0x31, 0xea, 0x58, 0x7a, 0xe2, 0xac, 0x2f,
0x36, 0xd2, 0x0f, 0x3b, 0x19, 0x33, 0x06, 0x0a, 0xd6, 0xef, 0x3e, 0x9d,
0x7d, 0x84, 0x75, 0xba, 0x01,
],
},
Signature {
index: 14,
signature: [
0xc7, 0x62, 0x16, 0x49, 0x49, 0x06, 0xae, 0xd8, 0xd8, 0x31, 0x0c, 0x31,
0xa5, 0xe4, 0xa5, 0x45, 0xea, 0x2a, 0xf8, 0xdb, 0xe3, 0x8e, 0xeb, 0x74,
0xa3, 0xd1, 0x4f, 0x07, 0x34, 0xdd, 0x2b, 0x1a, 0x21, 0xb9, 0x85, 0x0e,
0xa2, 0x0e, 0x6c, 0x2c, 0xa4, 0x22, 0x48, 0x78, 0x8b, 0xfb, 0x17, 0x43,
0x9d, 0x37, 0xab, 0xda, 0x25, 0xd7, 0x05, 0xb5, 0x68, 0xb2, 0x65, 0xb1,
0x13, 0xb9, 0x76, 0xc5, 0x00,
],
},
Signature {
index: 16,
signature: [
0xc6, 0x1f, 0xe6, 0xeb, 0xea, 0x61, 0xf2, 0xca, 0xe8, 0x95, 0xc3, 0x34,
0x96, 0x50, 0x70, 0x48, 0x7d, 0x39, 0xab, 0x6b, 0xf0, 0x44, 0x28, 0x34,
0x0a, 0xf0, 0xf7, 0x69, 0x9f, 0xdd, 0xd3, 0xc0, 0x1e, 0x0c, 0x86, 0xda,
0xea, 0x9e, 0xb4, 0x49, 0x70, 0x12, 0x48, 0xa2, 0x4f, 0xa4, 0xc0, 0xff,
0x75, 0xfb, 0x60, 0x0b, 0x2c, 0x01, 0x34, 0xbd, 0x72, 0x17, 0x91, 0xf6,
0x67, 0x11, 0x6e, 0x42, 0x00,
],
},
Signature {
index: 17,
signature: [
0x19, 0xb7, 0x9c, 0xaa, 0x25, 0x0e, 0x75, 0xda, 0x14, 0x82, 0xc7, 0xf4,
0x12, 0x68, 0x73, 0x08, 0xd1, 0x3e, 0xf7, 0x00, 0xf7, 0x26, 0xb9, 0x94,
0x17, 0xbb, 0x75, 0xae, 0x6d, 0xd1, 0x43, 0xfc, 0x61, 0x9f, 0x8c, 0x9c,
0x29, 0xc7, 0x3f, 0x99, 0x49, 0xaf, 0xfd, 0x16, 0x29, 0xcc, 0x28, 0x6a,
0x61, 0x91, 0x7e, 0xd7, 0x85, 0x37, 0x54, 0xa4, 0x67, 0x02, 0x92, 0x0b,
0x76, 0xcf, 0x90, 0xeb, 0x00,
],
},
Signature {
index: 18,
signature: [
0xb0, 0xba, 0xb5, 0xe1, 0xa8, 0xb3, 0x21, 0xe9, 0x2f, 0x9d, 0x3d, 0xf9,
0x24, 0x30, 0x18, 0x3e, 0x48, 0x43, 0xe5, 0x7c, 0x6f, 0x8c, 0x15, 0xc2,
0x2d, 0x62, 0xb2, 0xf8, 0xe4, 0xb8, 0xcd, 0xc2, 0x75, 0x9f, 0x28, 0x54,
0xb8, 0xd1, 0x22, 0xac, 0xdb, 0x4b, 0x4d, 0x89, 0x28, 0xb8, 0x7d, 0xf4,
0x19, 0x9c, 0xc6, 0x83, 0x01, 0x1d, 0x2e, 0x9b, 0x7d, 0x41, 0xa9, 0x6d,
0x8b, 0x48, 0x0c, 0xcc, 0x01,
],
},
],
timestamp: 0,
nonce: 969_194_102,
emitter_chain: Chain::Solana,
emitter_address: GOVERNANCE_EMITTER,
sequence: 2_694_510_404_604_284_400,
consistency_level: 32,
payload: GovernancePacket {
chain: Chain::Terra,
action: Action::ContractUpgrade {
new_contract: Address([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x83,
]),
},
},
};
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
let encoded = serde_json::to_string(&vaa).unwrap();
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
}
}

View File

@ -0,0 +1,64 @@
use std::{fmt, mem::MaybeUninit};
use serde::{
de::{Error, SeqAccess, Visitor},
ser::SerializeTuple,
Deserializer, Serializer,
};
pub fn serialize<const N: usize, S>(value: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_tuple(N)?;
for v in value {
seq.serialize_element(v)?;
}
seq.end()
}
struct ArrayVisitor<const N: usize>;
impl<'de, const N: usize> Visitor<'de> for ArrayVisitor<N> {
type Value = [u8; N];
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an array of length {}", N)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
// TODO: Replace with `MaybeUninit::uninit_array()` once that's stabilized.
let mut buf = MaybeUninit::<[u8; N]>::uninit();
let ptr = buf.as_mut_ptr() as *mut u8;
let mut pos = 0;
while pos < N {
let v = seq
.next_element()
.and_then(|v| v.ok_or_else(|| Error::invalid_length(pos, &self)))?;
// Safety: The resulting pointer is within the bounds of the allocation because
// we know that `pos < N`.
unsafe { ptr.add(pos).write(v) };
pos += 1;
}
if pos == N {
// Safety: We've initialized all the bytes in `buf`.
Ok(unsafe { buf.assume_init() })
} else {
Err(Error::invalid_length(pos, &self))
}
}
}
pub fn deserialize<'de, const N: usize, D>(deserializer: D) -> Result<[u8; N], D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_tuple(N, ArrayVisitor)
}

906
sdk/rust/core/src/token.rs Normal file
View File

@ -0,0 +1,906 @@
//! Parsers for Token bridge VAAs.
//!
//! Token bridging relies on VAA's that indicate custody/lockup/burn events in order to maintain
//! token parity between multiple chains. These parsers can be used to read these VAAs. It also
//! defines the Governance actions that this module supports, namely contract upgrades and chain
//! registrations.
use bstr::BString;
use serde::{Deserialize, Serialize};
use crate::{Address, Amount, Chain};
/// Represents a non-governance action targeted at the token bridge.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Message {
/// The Transfer message contains specifics detailing a token lock up on a sending chain. Chains
/// that are attempting to initiate a transfer must lock up tokens in some manner, such as in a
/// custody account or via burning, before emitting this message.
#[serde(rename = "1")]
Transfer {
/// The amount being transferred.
amount: Amount,
/// Address of the token. Left-zero-padded if shorter than 32 bytes.
token_address: Address,
/// Chain ID of the token.
token_chain: Chain,
/// Address of the recipient. Left-zero-padded if shorter than 32 bytes.
recipient: Address,
/// Chain ID of the recipient.
recipient_chain: Chain,
/// Amount that the user is willing to pay as the relayer fee. Must be <= `amount`.
fee: Amount,
},
/// Contains information about a token and its origin chain.
#[serde(rename = "2")]
AssetMeta {
/// Address of the token. Left-zero-padded if shorter than 32 bytes.
token_address: Address,
/// Chain ID of the token.
token_chain: Chain,
/// Number of decimals in the token.
decimals: u8,
/// Symbol of the token.
#[serde(with = "crate::arraystring")]
symbol: BString,
/// Name of the token.
#[serde(with = "crate::arraystring")]
name: BString,
},
/// Similar to `Transfer` but also includes an arbitrary payload that is appended to the end of
/// the message.
///
/// # Examples
///
/// Deserialize a `TransferWithPayload` message using the `serde_wormhole` crate:
///
/// ```
/// # fn example() -> anyhow::Result<()> {
/// # use wormhole::{Address, Amount, Chain, vaa::Signature, GOVERNANCE_EMITTER};
/// use wormhole::{token::Message, Vaa};
/// #
/// # let data = [
/// # 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d,
/// # 0x08, 0x90, 0x5c, 0x02, 0xe2, 0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb,
/// # 0x4f, 0x77, 0xfe, 0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9, 0x19,
/// # 0x4d, 0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2, 0x1c, 0xe9,
/// # 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01, 0x00, 0x00, 0x00,
/// # 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3c,
/// # 0x1b, 0xfa, 0x00, 0x03, 0x99, 0x30, 0x29, 0x01, 0x67, 0xf3, 0x48, 0x6a, 0xf6, 0x4c, 0xdd,
/// # 0x07, 0x64, 0xa1, 0x6a, 0xca, 0xcc, 0xc5, 0x64, 0x2a, 0x66, 0x71, 0xaa, 0x87, 0x98, 0x64,
/// # 0x0c, 0x25, 0x1f, 0x39, 0x73, 0x25, 0x12, 0x98, 0x15, 0xe5, 0x79, 0xdb, 0xd8, 0x25, 0xfd,
/// # 0x72, 0x58, 0x99, 0x97, 0xd1, 0x78, 0xb6, 0x2a, 0x57, 0x47, 0xab, 0xcf, 0x51, 0x62, 0xa5,
/// # 0xb7, 0xec, 0x19, 0x4e, 0x03, 0x51, 0x41, 0x18, 0x00, 0x08, 0xa4, 0x7e, 0xa2, 0x49, 0xd4,
/// # 0xf0, 0x56, 0x6f, 0xbd, 0x12, 0x77, 0xfe, 0xa8, 0x6e, 0x92, 0x81, 0x3a, 0x35, 0x90, 0x3b,
/// # 0x29, 0x73, 0x4d, 0x75, 0x56, 0x0a, 0xfe, 0x70, 0xb0, 0xb9, 0x10, 0x33, 0x00, 0x14, 0x47,
/// # 0x3e, 0x51, 0x74, 0x32, 0xbc, 0x3f, 0xb3, 0xf9, 0x82, 0xbb, 0xf3, 0xc9, 0x43, 0x41, 0xcf,
/// # 0x74, 0x24, 0xff, 0xa4, 0x02, 0x35, 0xbe, 0xb1, 0x7c, 0x47, 0x16, 0xba, 0xbc, 0xaa, 0xbe,
/// # 0x99, 0x54, 0xa2, 0x97, 0xbe, 0x2a, 0xed, 0xae, 0x91, 0x95, 0xb9, 0x6a, 0x8a, 0xba, 0xbd,
/// # 0x3a, 0xc1, 0xa4, 0xd1, 0x96, 0x0a, 0xf9, 0xab, 0x92, 0x42, 0x0d, 0x41, 0x33, 0xe5, 0xa8,
/// # 0x37, 0x32, 0xd3, 0xc3, 0xb8, 0xf3, 0x93, 0xd7, 0xc0, 0x9e, 0xe8, 0x87, 0xae, 0x16, 0xbf,
/// # 0xfa, 0x5e, 0x70, 0xea, 0x36, 0xa2, 0x82, 0x37, 0x1d, 0x46, 0x81, 0x94, 0x10, 0x34, 0xb1,
/// # 0xad, 0x0f, 0x4b, 0xc9, 0x17, 0x1e, 0x91, 0x25, 0x11,
/// # ];
///
/// let (msg, payload) = serde_wormhole::from_slice_with_payload::<Vaa<Message>>(&data)?;
/// assert!(matches!(msg.payload, Message::TransferWithPayload { .. }));
/// assert_eq!(&data[256..], payload);
/// #
/// # Ok(())
/// # }
/// #
/// # example().unwrap();
/// ```
///
/// Deserialize a `TransferWithPayload` message using `serde_json`:
///
/// ```
/// # fn example() -> anyhow::Result<()> {
/// # use wormhole::{Address, Amount, Chain, vaa::Signature, GOVERNANCE_EMITTER};
/// use anyhow::anyhow;
/// use wormhole::{token::Message, Vaa};
/// # let vaa = Vaa {
/// # version: 1,
/// # guardian_set_index: 0,
/// # signatures: Vec::new(),
/// # timestamp: 1,
/// # nonce: 1,
/// # emitter_chain: Chain::Solana,
/// # emitter_address: GOVERNANCE_EMITTER,
/// # sequence: 20_716_538,
/// # consistency_level: 0,
/// # payload: Message::TransferWithPayload {
/// # amount: Amount([0xcc; 32]),
/// # token_address: Address([0x22; 32]),
/// # token_chain: Chain::Algorand,
/// # recipient: Address([0x19; 32]),
/// # recipient_chain: Chain::Osmosis,
/// # sender_address: Address([0xfe; 32]),
/// # },
/// # };
/// #
/// # let payload = [
/// # 0x93, 0xd7, 0xc0, 0x9e, 0xe8, 0x87, 0xae, 0x16, 0xbf, 0xfa, 0x5e, 0x70, 0xea, 0x36, 0xa2,
/// # 0x82, 0x37, 0x1d, 0x46, 0x81, 0x94, 0x10, 0x34, 0xb1, 0xad, 0x0f, 0x4b, 0xc9,
/// # ];
/// #
/// # let mut data = serde_json::to_vec(&vaa)?;
/// # data.extend_from_slice(&payload);
///
/// let mut stream = serde_json::Deserializer::from_slice(&data).into_iter();
/// let msg: Vaa<Message> = stream
/// .next()
/// .ok_or_else(|| anyhow!("missing token message"))??;
/// assert!(matches!(msg.payload, Message::TransferWithPayload { .. }));
/// assert_eq!(&data[stream.byte_offset()..], payload);
/// #
/// # assert_eq!(vaa, msg);
/// # Ok(())
/// # }
/// #
/// # example().unwrap();
/// ```
#[serde(rename = "3")]
TransferWithPayload {
/// The amount being transferred.
amount: Amount,
/// Address of the token. Left-zero-padded if shorter than 32 bytes.
token_address: Address,
/// Chain ID of the token.
token_chain: Chain,
/// Address of the recipient. Left-zero-padded if shorter than 32 bytes.
recipient: Address,
/// Chain ID of the recipient.
recipient_chain: Chain,
/// The identity of the sender sending the payload.
sender_address: Address,
// The actual payload is appended to the end of the message.
},
}
/// Represents a governance action targeted at the token bridge.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Action {
/// Registers an emitter address for a particular chain on a different chain. An emitter
/// address must be registered for a chain and must match the emitter address in the VAA before
/// the token bridge will accept VAAs from that chain.
#[serde(rename = "1")]
RegisterChain {
chain: Chain,
emitter_address: Address,
},
/// Upgrades the token bridge contract to a new address.
#[serde(rename = "2")]
ContractUpgrade { new_contract: Address },
}
/// Represents the payload for a governance VAA targeted at the token bridge.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GovernancePacket {
/// The chain on which the governance action should be carried out.
pub chain: Chain,
/// The actual governance action to be carried out.
pub action: Action,
}
// The wire format for GovernancePackets is wonky and doesn't lend itself well to auto-deriving
// Serialize / Deserialize so we implement it manually here.
mod governance_packet_impl {
use std::fmt;
use serde::{
de::{Error, MapAccess, SeqAccess, Unexpected, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
use crate::{
token::{Action, GovernancePacket},
Address, Chain,
};
// MODULE = "TokenBridge"
const MODULE: [u8; 32] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x72, 0x69, 0x64,
0x67, 0x65,
];
struct Module;
impl Serialize for Module {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
MODULE.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Module {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let arr = <[u8; 32]>::deserialize(deserializer)?;
if arr == MODULE {
Ok(Module)
} else {
let expected = format!("{MODULE:?}");
Err(Error::invalid_value(Unexpected::Bytes(&arr), &&*expected))
}
}
}
#[derive(Serialize, Deserialize)]
struct ContractUpgrade {
new_contract: Address,
}
#[derive(Serialize, Deserialize)]
struct RegisterChain {
chain: Chain,
emitter_address: Address,
}
impl Serialize for GovernancePacket {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_struct("GovernancePacket", 4)?;
seq.serialize_field("module", &Module)?;
// The wire format encodes the action before the chain and then appends the actual
// action payload.
match self.action {
Action::RegisterChain {
chain,
emitter_address,
} => {
seq.serialize_field("action", &1u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field(
"payload",
&RegisterChain {
chain,
emitter_address,
},
)?;
}
Action::ContractUpgrade { new_contract } => {
seq.serialize_field("action", &2u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field("payload", &ContractUpgrade { new_contract })?;
}
}
seq.end()
}
}
struct GovernancePacketVisitor;
impl<'de> Visitor<'de> for GovernancePacketVisitor {
type Value = GovernancePacket;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("struct GovernancePacket")
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
static EXPECTING: &str = "struct GovernancePacket with 4 elements";
let _: Module = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(0, &EXPECTING))?;
let act: u8 = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(1, &EXPECTING))?;
let chain = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(2, &EXPECTING))?;
let action = match act {
1 => {
let RegisterChain {
chain,
emitter_address,
} = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::RegisterChain {
chain,
emitter_address,
}
}
2 => {
let ContractUpgrade { new_contract } = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::ContractUpgrade { new_contract }
}
v => {
return Err(Error::invalid_value(
Unexpected::Unsigned(v.into()),
&"one of 1, 2",
))
}
};
Ok(GovernancePacket { chain, action })
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Field {
Module,
Action,
Chain,
Payload,
}
let mut module = None;
let mut chain = None;
let mut action = None;
let mut payload = None;
while let Some(key) = map.next_key::<Field>()? {
match key {
Field::Module => {
if module.is_some() {
return Err(Error::duplicate_field("module"));
}
module = map.next_value::<Module>().map(Some)?;
}
Field::Action => {
if action.is_some() {
return Err(Error::duplicate_field("action"));
}
action = map.next_value::<u8>().map(Some)?;
}
Field::Chain => {
if chain.is_some() {
return Err(Error::duplicate_field("chain"));
}
chain = map.next_value().map(Some)?;
}
Field::Payload => {
if payload.is_some() {
return Err(Error::duplicate_field("payload"));
}
let a = action.as_ref().copied().ok_or_else(|| {
Error::custom("`action` must be known before deserializing `payload`")
})?;
let p = match a {
1 => {
let RegisterChain {
chain,
emitter_address,
} = map.next_value()?;
Action::RegisterChain {
chain,
emitter_address,
}
}
2 => {
let ContractUpgrade { new_contract } = map.next_value()?;
Action::ContractUpgrade { new_contract }
}
v => {
return Err(Error::custom(format_args!(
"invalid action: {v}, expected one of: 1, 2"
)))
}
};
payload = Some(p);
}
}
}
let _ = module.ok_or_else(|| Error::missing_field("module"))?;
let chain = chain.ok_or_else(|| Error::missing_field("chain"))?;
let action = payload.ok_or_else(|| Error::missing_field("payload"))?;
Ok(GovernancePacket { chain, action })
}
}
impl<'de> Deserialize<'de> for GovernancePacket {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &[&str] = &["module", "action", "chain", "payload"];
deserializer.deserialize_struct("GovernancePacket", FIELDS, GovernancePacketVisitor)
}
}
}
#[cfg(test)]
mod test {
use crate::{vaa::Signature, Vaa, GOVERNANCE_EMITTER};
use super::*;
#[test]
pub fn transfer() {
let payload = [
0x01, 0xfa, 0x22, 0x8b, 0x3d, 0xf3, 0x59, 0x21, 0xa1, 0xc9, 0x39, 0xad, 0x9c, 0x54,
0xe1, 0x2f, 0x87, 0xc6, 0x27, 0x25, 0xd1, 0xaf, 0x7c, 0x7b, 0x6a, 0xea, 0xbf, 0x48,
0x14, 0xe7, 0x26, 0x56, 0xfa, 0xe8, 0xf2, 0xd2, 0x53, 0xd1, 0x08, 0x52, 0xa2, 0x6f,
0x55, 0xae, 0x93, 0xb5, 0x9b, 0x46, 0x91, 0x3d, 0xdf, 0x89, 0x1c, 0xb5, 0x38, 0x75,
0x5a, 0x45, 0x47, 0xde, 0x51, 0x12, 0x01, 0x5d, 0xc3, 0x00, 0x15, 0x7a, 0x1f, 0x37,
0xe7, 0x14, 0x28, 0xbc, 0x04, 0x75, 0xd9, 0x26, 0x8e, 0x4b, 0x53, 0x06, 0xd3, 0xa1,
0x22, 0x40, 0x13, 0xc4, 0x36, 0xbd, 0x1c, 0x23, 0xd7, 0x2a, 0x1c, 0x16, 0x46, 0x43,
0x50, 0x00, 0x07, 0x48, 0xc7, 0x7c, 0x80, 0xbd, 0x11, 0xee, 0x41, 0x20, 0xb3, 0x52,
0x3b, 0xd0, 0x0a, 0x2f, 0xdd, 0x02, 0xe8, 0xef, 0xfc, 0x7a, 0xfe, 0x3d, 0xd6, 0x73,
0x2c, 0x5d, 0xa0, 0x6b, 0x08, 0x98, 0x6c,
];
let msg = Message::Transfer {
amount: Amount([
0xfa, 0x22, 0x8b, 0x3d, 0xf3, 0x59, 0x21, 0xa1, 0xc9, 0x39, 0xad, 0x9c, 0x54, 0xe1,
0x2f, 0x87, 0xc6, 0x27, 0x25, 0xd1, 0xaf, 0x7c, 0x7b, 0x6a, 0xea, 0xbf, 0x48, 0x14,
0xe7, 0x26, 0x56, 0xfa,
]),
token_address: Address([
0xe8, 0xf2, 0xd2, 0x53, 0xd1, 0x08, 0x52, 0xa2, 0x6f, 0x55, 0xae, 0x93, 0xb5, 0x9b,
0x46, 0x91, 0x3d, 0xdf, 0x89, 0x1c, 0xb5, 0x38, 0x75, 0x5a, 0x45, 0x47, 0xde, 0x51,
0x12, 0x01, 0x5d, 0xc3,
]),
token_chain: Chain::Sui,
recipient: Address([
0x7a, 0x1f, 0x37, 0xe7, 0x14, 0x28, 0xbc, 0x04, 0x75, 0xd9, 0x26, 0x8e, 0x4b, 0x53,
0x06, 0xd3, 0xa1, 0x22, 0x40, 0x13, 0xc4, 0x36, 0xbd, 0x1c, 0x23, 0xd7, 0x2a, 0x1c,
0x16, 0x46, 0x43, 0x50,
]),
recipient_chain: Chain::Oasis,
fee: Amount([
0x48, 0xc7, 0x7c, 0x80, 0xbd, 0x11, 0xee, 0x41, 0x20, 0xb3, 0x52, 0x3b, 0xd0, 0x0a,
0x2f, 0xdd, 0x02, 0xe8, 0xef, 0xfc, 0x7a, 0xfe, 0x3d, 0xd6, 0x73, 0x2c, 0x5d, 0xa0,
0x6b, 0x08, 0x98, 0x6c,
]),
};
assert_eq!(payload.as_ref(), &serde_wormhole::to_vec(&msg).unwrap());
assert_eq!(msg, serde_wormhole::from_slice(&payload).unwrap());
let encoded = serde_json::to_string(&msg).unwrap();
assert_eq!(msg, serde_json::from_str(&encoded).unwrap());
}
#[test]
pub fn asset_meta() {
let payload = [
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xbe, 0xef, 0xfa, 0xce, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x45, 0x45, 0x46, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x42, 0x65, 0x65, 0x66, 0x20, 0x66, 0x61, 0x63, 0x65, 0x20, 0x54, 0x6f, 0x6b,
0x65, 0x6e,
];
let msg = Message::AssetMeta {
token_address: Address([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xbe, 0xef, 0xfa, 0xce,
]),
token_chain: Chain::Ethereum,
decimals: 12,
symbol: "BEEF".into(),
name: "Beef face Token".into(),
};
assert_eq!(payload.as_ref(), &serde_wormhole::to_vec(&msg).unwrap());
assert_eq!(msg, serde_wormhole::from_slice(&payload).unwrap());
let encoded = serde_json::to_string(&msg).unwrap();
assert_eq!(msg, serde_json::from_str(&encoded).unwrap());
}
#[test]
pub fn transfer_with_payload() {
let payload = [
0x03, 0x99, 0x30, 0x29, 0x01, 0x67, 0xf3, 0x48, 0x6a, 0xf6, 0x4c, 0xdd, 0x07, 0x64,
0xa1, 0x6a, 0xca, 0xcc, 0xc5, 0x64, 0x2a, 0x66, 0x71, 0xaa, 0x87, 0x98, 0x64, 0x0c,
0x25, 0x1f, 0x39, 0x73, 0x25, 0x12, 0x98, 0x15, 0xe5, 0x79, 0xdb, 0xd8, 0x25, 0xfd,
0x72, 0x58, 0x99, 0x97, 0xd1, 0x78, 0xb6, 0x2a, 0x57, 0x47, 0xab, 0xcf, 0x51, 0x62,
0xa5, 0xb7, 0xec, 0x19, 0x4e, 0x03, 0x51, 0x41, 0x18, 0x00, 0x08, 0xa4, 0x7e, 0xa2,
0x49, 0xd4, 0xf0, 0x56, 0x6f, 0xbd, 0x12, 0x77, 0xfe, 0xa8, 0x6e, 0x92, 0x81, 0x3a,
0x35, 0x90, 0x3b, 0x29, 0x73, 0x4d, 0x75, 0x56, 0x0a, 0xfe, 0x70, 0xb0, 0xb9, 0x10,
0x33, 0x00, 0x14, 0x47, 0x3e, 0x51, 0x74, 0x32, 0xbc, 0x3f, 0xb3, 0xf9, 0x82, 0xbb,
0xf3, 0xc9, 0x43, 0x41, 0xcf, 0x74, 0x24, 0xff, 0xa4, 0x02, 0x35, 0xbe, 0xb1, 0x7c,
0x47, 0x16, 0xba, 0xbc, 0xaa, 0xbe, 0x99, 0x54, 0xa2, 0x97, 0xbe, 0x2a, 0xed, 0xae,
0x91, 0x95, 0xb9, 0x6a, 0x8a, 0xba, 0xbd, 0x3a, 0xc1, 0xa4, 0xd1, 0x96, 0x0a, 0xf9,
0xab, 0x92, 0x42, 0x0d, 0x41, 0x33, 0xe5, 0xa8, 0x37, 0x32, 0xd3, 0xc3, 0xb8, 0xf3,
0x93, 0xd7, 0xc0, 0x9e, 0xe8, 0x87, 0xae, 0x16, 0xbf, 0xfa, 0x5e, 0x70, 0xea, 0x36,
0xa2, 0x82, 0x37, 0x1d, 0x46, 0x81, 0x94, 0x10, 0x34, 0xb1, 0xad, 0x0f, 0x4b, 0xc9,
0x17, 0x1e, 0x91, 0x25, 0x11,
];
let msg = Message::TransferWithPayload {
amount: Amount([
0x99, 0x30, 0x29, 0x01, 0x67, 0xf3, 0x48, 0x6a, 0xf6, 0x4c, 0xdd, 0x07, 0x64, 0xa1,
0x6a, 0xca, 0xcc, 0xc5, 0x64, 0x2a, 0x66, 0x71, 0xaa, 0x87, 0x98, 0x64, 0x0c, 0x25,
0x1f, 0x39, 0x73, 0x25,
]),
token_address: Address([
0x12, 0x98, 0x15, 0xe5, 0x79, 0xdb, 0xd8, 0x25, 0xfd, 0x72, 0x58, 0x99, 0x97, 0xd1,
0x78, 0xb6, 0x2a, 0x57, 0x47, 0xab, 0xcf, 0x51, 0x62, 0xa5, 0xb7, 0xec, 0x19, 0x4e,
0x03, 0x51, 0x41, 0x18,
]),
token_chain: Chain::Algorand,
recipient: Address([
0xa4, 0x7e, 0xa2, 0x49, 0xd4, 0xf0, 0x56, 0x6f, 0xbd, 0x12, 0x77, 0xfe, 0xa8, 0x6e,
0x92, 0x81, 0x3a, 0x35, 0x90, 0x3b, 0x29, 0x73, 0x4d, 0x75, 0x56, 0x0a, 0xfe, 0x70,
0xb0, 0xb9, 0x10, 0x33,
]),
recipient_chain: Chain::Osmosis,
sender_address: Address([
0x47, 0x3e, 0x51, 0x74, 0x32, 0xbc, 0x3f, 0xb3, 0xf9, 0x82, 0xbb, 0xf3, 0xc9, 0x43,
0x41, 0xcf, 0x74, 0x24, 0xff, 0xa4, 0x02, 0x35, 0xbe, 0xb1, 0x7c, 0x47, 0x16, 0xba,
0xbc, 0xaa, 0xbe, 0x99,
]),
};
assert_eq!(&payload[..133], &serde_wormhole::to_vec(&msg).unwrap());
let (actual, data) = serde_wormhole::from_slice_with_payload(&payload).unwrap();
assert_eq!(msg, actual);
assert_eq!(&payload[133..], data);
let mut encoded = serde_json::to_vec(&msg).unwrap();
encoded.extend_from_slice(&payload[133..]);
let mut stream = serde_json::Deserializer::from_slice(&encoded).into_iter();
assert_eq!(msg, stream.next().unwrap().unwrap());
assert_eq!(payload[133..], encoded[stream.byte_offset()..]);
}
#[test]
fn register_chain() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c,
0x1d, 0x08, 0x90, 0x5c, 0x02, 0xe2, 0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6,
0xc8, 0xdb, 0x4f, 0x77, 0xfe, 0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e,
0x92, 0xa9, 0x19, 0x4d, 0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6,
0xf0, 0xc2, 0x1c, 0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e,
0x98, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3c, 0x1b, 0xfa, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x72, 0x69, 0x64, 0x67,
0x65, 0x01, 0x00, 0x00, 0x00, 0x01, 0x3b, 0x26, 0x40, 0x9f, 0x8a, 0xad, 0xed, 0x3f,
0x5d, 0xdc, 0xa1, 0x84, 0x69, 0x5a, 0xa6, 0xa0, 0xfa, 0x82, 0x9b, 0x0c, 0x85, 0xca,
0xf8, 0x48, 0x56, 0x32, 0x48, 0x96, 0xd2, 0x14, 0xca, 0x98,
];
let vaa = Vaa {
version: 1,
guardian_set_index: 0,
signatures: vec![Signature {
index: 0,
signature: [
0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d, 0x08, 0x90, 0x5c, 0x02, 0xe2,
0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb, 0x4f, 0x77, 0xfe,
0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9, 0x19, 0x4d,
0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2, 0x1c,
0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01,
],
}],
timestamp: 1,
nonce: 1,
emitter_chain: Chain::Solana,
emitter_address: GOVERNANCE_EMITTER,
sequence: 20_716_538,
consistency_level: 0,
payload: GovernancePacket {
chain: Chain::Any,
action: Action::RegisterChain {
chain: Chain::Solana,
emitter_address: Address([
0x3b, 0x26, 0x40, 0x9f, 0x8a, 0xad, 0xed, 0x3f, 0x5d, 0xdc, 0xa1, 0x84,
0x69, 0x5a, 0xa6, 0xa0, 0xfa, 0x82, 0x9b, 0x0c, 0x85, 0xca, 0xf8, 0x48,
0x56, 0x32, 0x48, 0x96, 0xd2, 0x14, 0xca, 0x98,
]),
},
},
};
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
let encoded = serde_json::to_string(&vaa).unwrap();
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
}
#[test]
fn contract_upgrade() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x73, 0xaf, 0x3c, 0x2f, 0x55, 0x98, 0x90,
0x41, 0xb0, 0x06, 0x8a, 0x4e, 0xae, 0xba, 0x8f, 0x88, 0x25, 0x10, 0x60, 0x21, 0x99,
0x6d, 0xe6, 0x14, 0x39, 0x54, 0xc0, 0x6f, 0xc4, 0xcf, 0xad, 0x5c, 0x3f, 0x1a, 0xba,
0x6c, 0xa9, 0x51, 0x18, 0x8e, 0x96, 0x2f, 0x11, 0x25, 0x1c, 0x25, 0xca, 0x98, 0x62,
0x86, 0x85, 0xe2, 0xfc, 0x2b, 0x65, 0xec, 0x60, 0x94, 0x50, 0xcf, 0xc4, 0xe6, 0x51,
0x12, 0x00, 0x03, 0x00, 0xc6, 0x2e, 0xb0, 0xde, 0x7a, 0xb3, 0xd0, 0x28, 0x92, 0x0f,
0x18, 0xe1, 0x0d, 0xf7, 0xf2, 0xa0, 0x4d, 0x00, 0xeb, 0x88, 0xb8, 0x94, 0x0a, 0x27,
0xbb, 0xce, 0x8c, 0xe6, 0x6c, 0x67, 0x52, 0x68, 0x82, 0xc9, 0x6c, 0xc5, 0x0d, 0xd3,
0x81, 0xbd, 0xbf, 0xf3, 0x0b, 0xb6, 0x5b, 0x93, 0x47, 0xca, 0xdf, 0xf0, 0x96, 0x76,
0x72, 0x90, 0x43, 0xaf, 0x60, 0xd6, 0x52, 0xa0, 0xb9, 0x06, 0x30, 0x00, 0x04, 0xf0,
0x7b, 0x0f, 0xe6, 0xca, 0xb1, 0x8b, 0xe7, 0xae, 0x82, 0x7f, 0xe1, 0x1c, 0xa8, 0x99,
0x2f, 0xf0, 0xb5, 0xa2, 0xa3, 0x2d, 0x65, 0xbe, 0x95, 0x82, 0x31, 0xbf, 0x84, 0xcf,
0x6b, 0x14, 0xfb, 0x5e, 0xeb, 0x8a, 0x51, 0xad, 0xe1, 0xc7, 0x0a, 0xa5, 0xb8, 0xb8,
0xcf, 0x83, 0xaf, 0xe3, 0xc2, 0x2d, 0x34, 0x71, 0x48, 0x1b, 0xda, 0x38, 0x96, 0xea,
0x2a, 0x70, 0x46, 0x9f, 0x96, 0x92, 0xb8, 0x00, 0x05, 0x74, 0xd4, 0x96, 0x72, 0x9d,
0xde, 0x6e, 0x19, 0x09, 0x59, 0xa1, 0x79, 0x45, 0xe8, 0x7c, 0xd9, 0x5e, 0x23, 0xc1,
0x00, 0x47, 0xab, 0x2e, 0xef, 0x5c, 0x7d, 0x0e, 0xc6, 0xf9, 0x12, 0x08, 0xc7, 0x27,
0x9d, 0x8a, 0x1b, 0x1f, 0x4a, 0x60, 0x06, 0x9d, 0x25, 0x93, 0x2a, 0x67, 0xd6, 0x55,
0xf2, 0xd0, 0xb4, 0x20, 0x59, 0x00, 0x3d, 0xe7, 0x5f, 0xd3, 0xca, 0x79, 0x92, 0xfd,
0xd1, 0xb0, 0xd4, 0x01, 0x06, 0x51, 0xf0, 0x6a, 0xb6, 0xfd, 0xfd, 0x42, 0xf3, 0x23,
0x84, 0x0a, 0x81, 0x8b, 0x38, 0xcf, 0xd1, 0xc9, 0x9d, 0x22, 0x36, 0x6c, 0x87, 0x79,
0x03, 0xb9, 0x8d, 0xf4, 0x0b, 0xda, 0xf2, 0xa2, 0x04, 0x2e, 0xb8, 0xcd, 0x0e, 0x59,
0xc4, 0x63, 0x7e, 0x6a, 0x80, 0xa1, 0x99, 0xb1, 0x10, 0x8c, 0xd7, 0x65, 0x9b, 0xa8,
0x37, 0x0f, 0x6f, 0x94, 0x76, 0xb4, 0x79, 0x83, 0x98, 0x54, 0xd8, 0xc1, 0xa6, 0x00,
0x07, 0x78, 0x4b, 0x37, 0xc6, 0x10, 0x3c, 0x75, 0x2c, 0xd9, 0x7a, 0x58, 0x6b, 0xe8,
0xe1, 0xce, 0xff, 0x22, 0xa6, 0xe4, 0x88, 0x43, 0x32, 0x84, 0xff, 0x31, 0xcd, 0x90,
0x8b, 0x5c, 0x7d, 0x87, 0xe2, 0x44, 0xda, 0x75, 0x16, 0xd0, 0x7a, 0x0f, 0xa6, 0x92,
0x68, 0x4f, 0x82, 0x6e, 0x5e, 0x6c, 0x8b, 0x45, 0x20, 0x04, 0x20, 0x6d, 0x6f, 0xe5,
0xba, 0xe4, 0x6a, 0xfb, 0x1c, 0x21, 0x8c, 0xf5, 0x0d, 0x00, 0x09, 0x0b, 0x6c, 0xcc,
0xe6, 0x7e, 0xd5, 0x01, 0xcb, 0x20, 0xfb, 0x06, 0xde, 0x76, 0xb9, 0x08, 0x3f, 0x13,
0x29, 0xad, 0x78, 0xd3, 0x84, 0x51, 0x7a, 0x94, 0xab, 0x8e, 0x9a, 0xa6, 0x01, 0xbe,
0x7a, 0x0f, 0x00, 0xbb, 0x60, 0x3b, 0x36, 0x50, 0x4a, 0x74, 0xdd, 0xc3, 0x67, 0x35,
0x9c, 0x8e, 0xa0, 0x7f, 0x5c, 0xcd, 0x50, 0x22, 0x77, 0x2c, 0xc1, 0x57, 0xe0, 0x0f,
0x54, 0x15, 0x63, 0x71, 0xe4, 0x01, 0x0b, 0xe1, 0xe6, 0xb7, 0x12, 0xa2, 0x35, 0x3e,
0x49, 0x07, 0x92, 0x9c, 0x40, 0x57, 0xd5, 0xba, 0xbe, 0xf8, 0x0d, 0xc0, 0x9f, 0xeb,
0xb3, 0xdc, 0x29, 0x34, 0xf4, 0x40, 0x48, 0x79, 0x5e, 0xef, 0x95, 0x4e, 0x99, 0x10,
0x6c, 0xb2, 0x9b, 0x1a, 0x8d, 0x93, 0x53, 0x69, 0xda, 0xec, 0xbb, 0x8b, 0xae, 0x45,
0x19, 0x2e, 0xfc, 0x3c, 0xed, 0xbc, 0x57, 0x31, 0x5d, 0x67, 0xec, 0x9e, 0xa8, 0x00,
0x0a, 0x00, 0x0d, 0xdc, 0xc0, 0x4c, 0xd7, 0x55, 0x6a, 0x94, 0xe8, 0xb4, 0x18, 0xdc,
0x3a, 0x8a, 0x00, 0xef, 0xb1, 0x28, 0xd4, 0xf7, 0x6f, 0x71, 0x43, 0x12, 0x47, 0xa9,
0x2e, 0x7a, 0x19, 0xa8, 0x33, 0xfc, 0x02, 0x21, 0x5d, 0x1f, 0xd6, 0x9e, 0x0e, 0x59,
0x6d, 0xa5, 0x31, 0xea, 0x58, 0x7a, 0xe2, 0xac, 0x2f, 0x36, 0xd2, 0x0f, 0x3b, 0x19,
0x33, 0x06, 0x0a, 0xd6, 0xef, 0x3e, 0x9d, 0x7d, 0x84, 0x75, 0xba, 0x01, 0x0e, 0xc7,
0x62, 0x16, 0x49, 0x49, 0x06, 0xae, 0xd8, 0xd8, 0x31, 0x0c, 0x31, 0xa5, 0xe4, 0xa5,
0x45, 0xea, 0x2a, 0xf8, 0xdb, 0xe3, 0x8e, 0xeb, 0x74, 0xa3, 0xd1, 0x4f, 0x07, 0x34,
0xdd, 0x2b, 0x1a, 0x21, 0xb9, 0x85, 0x0e, 0xa2, 0x0e, 0x6c, 0x2c, 0xa4, 0x22, 0x48,
0x78, 0x8b, 0xfb, 0x17, 0x43, 0x9d, 0x37, 0xab, 0xda, 0x25, 0xd7, 0x05, 0xb5, 0x68,
0xb2, 0x65, 0xb1, 0x13, 0xb9, 0x76, 0xc5, 0x00, 0x10, 0xc6, 0x1f, 0xe6, 0xeb, 0xea,
0x61, 0xf2, 0xca, 0xe8, 0x95, 0xc3, 0x34, 0x96, 0x50, 0x70, 0x48, 0x7d, 0x39, 0xab,
0x6b, 0xf0, 0x44, 0x28, 0x34, 0x0a, 0xf0, 0xf7, 0x69, 0x9f, 0xdd, 0xd3, 0xc0, 0x1e,
0x0c, 0x86, 0xda, 0xea, 0x9e, 0xb4, 0x49, 0x70, 0x12, 0x48, 0xa2, 0x4f, 0xa4, 0xc0,
0xff, 0x75, 0xfb, 0x60, 0x0b, 0x2c, 0x01, 0x34, 0xbd, 0x72, 0x17, 0x91, 0xf6, 0x67,
0x11, 0x6e, 0x42, 0x00, 0x11, 0x19, 0xb7, 0x9c, 0xaa, 0x25, 0x0e, 0x75, 0xda, 0x14,
0x82, 0xc7, 0xf4, 0x12, 0x68, 0x73, 0x08, 0xd1, 0x3e, 0xf7, 0x00, 0xf7, 0x26, 0xb9,
0x94, 0x17, 0xbb, 0x75, 0xae, 0x6d, 0xd1, 0x43, 0xfc, 0x61, 0x9f, 0x8c, 0x9c, 0x29,
0xc7, 0x3f, 0x99, 0x49, 0xaf, 0xfd, 0x16, 0x29, 0xcc, 0x28, 0x6a, 0x61, 0x91, 0x7e,
0xd7, 0x85, 0x37, 0x54, 0xa4, 0x67, 0x02, 0x92, 0x0b, 0x76, 0xcf, 0x90, 0xeb, 0x00,
0x12, 0xb0, 0xba, 0xb5, 0xe1, 0xa8, 0xb3, 0x21, 0xe9, 0x2f, 0x9d, 0x3d, 0xf9, 0x24,
0x30, 0x18, 0x3e, 0x48, 0x43, 0xe5, 0x7c, 0x6f, 0x8c, 0x15, 0xc2, 0x2d, 0x62, 0xb2,
0xf8, 0xe4, 0xb8, 0xcd, 0xc2, 0x75, 0x9f, 0x28, 0x54, 0xb8, 0xd1, 0x22, 0xac, 0xdb,
0x4b, 0x4d, 0x89, 0x28, 0xb8, 0x7d, 0xf4, 0x19, 0x9c, 0xc6, 0x83, 0x01, 0x1d, 0x2e,
0x9b, 0x7d, 0x41, 0xa9, 0x6d, 0x8b, 0x48, 0x0c, 0xcc, 0x01, 0x00, 0x00, 0x00, 0x00,
0x39, 0xc4, 0xba, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x25, 0x64, 0xd2, 0xef,
0xd6, 0x08, 0x4d, 0xf0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x02, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x09, 0x83,
];
let vaa = Vaa {
version: 1,
guardian_set_index: 1,
signatures: vec![
Signature {
index: 2,
signature: [
0x73, 0xaf, 0x3c, 0x2f, 0x55, 0x98, 0x90, 0x41, 0xb0, 0x06, 0x8a, 0x4e,
0xae, 0xba, 0x8f, 0x88, 0x25, 0x10, 0x60, 0x21, 0x99, 0x6d, 0xe6, 0x14,
0x39, 0x54, 0xc0, 0x6f, 0xc4, 0xcf, 0xad, 0x5c, 0x3f, 0x1a, 0xba, 0x6c,
0xa9, 0x51, 0x18, 0x8e, 0x96, 0x2f, 0x11, 0x25, 0x1c, 0x25, 0xca, 0x98,
0x62, 0x86, 0x85, 0xe2, 0xfc, 0x2b, 0x65, 0xec, 0x60, 0x94, 0x50, 0xcf,
0xc4, 0xe6, 0x51, 0x12, 0x00,
],
},
Signature {
index: 3,
signature: [
0x00, 0xc6, 0x2e, 0xb0, 0xde, 0x7a, 0xb3, 0xd0, 0x28, 0x92, 0x0f, 0x18,
0xe1, 0x0d, 0xf7, 0xf2, 0xa0, 0x4d, 0x00, 0xeb, 0x88, 0xb8, 0x94, 0x0a,
0x27, 0xbb, 0xce, 0x8c, 0xe6, 0x6c, 0x67, 0x52, 0x68, 0x82, 0xc9, 0x6c,
0xc5, 0x0d, 0xd3, 0x81, 0xbd, 0xbf, 0xf3, 0x0b, 0xb6, 0x5b, 0x93, 0x47,
0xca, 0xdf, 0xf0, 0x96, 0x76, 0x72, 0x90, 0x43, 0xaf, 0x60, 0xd6, 0x52,
0xa0, 0xb9, 0x06, 0x30, 0x00,
],
},
Signature {
index: 4,
signature: [
0xf0, 0x7b, 0x0f, 0xe6, 0xca, 0xb1, 0x8b, 0xe7, 0xae, 0x82, 0x7f, 0xe1,
0x1c, 0xa8, 0x99, 0x2f, 0xf0, 0xb5, 0xa2, 0xa3, 0x2d, 0x65, 0xbe, 0x95,
0x82, 0x31, 0xbf, 0x84, 0xcf, 0x6b, 0x14, 0xfb, 0x5e, 0xeb, 0x8a, 0x51,
0xad, 0xe1, 0xc7, 0x0a, 0xa5, 0xb8, 0xb8, 0xcf, 0x83, 0xaf, 0xe3, 0xc2,
0x2d, 0x34, 0x71, 0x48, 0x1b, 0xda, 0x38, 0x96, 0xea, 0x2a, 0x70, 0x46,
0x9f, 0x96, 0x92, 0xb8, 0x00,
],
},
Signature {
index: 5,
signature: [
0x74, 0xd4, 0x96, 0x72, 0x9d, 0xde, 0x6e, 0x19, 0x09, 0x59, 0xa1, 0x79,
0x45, 0xe8, 0x7c, 0xd9, 0x5e, 0x23, 0xc1, 0x00, 0x47, 0xab, 0x2e, 0xef,
0x5c, 0x7d, 0x0e, 0xc6, 0xf9, 0x12, 0x08, 0xc7, 0x27, 0x9d, 0x8a, 0x1b,
0x1f, 0x4a, 0x60, 0x06, 0x9d, 0x25, 0x93, 0x2a, 0x67, 0xd6, 0x55, 0xf2,
0xd0, 0xb4, 0x20, 0x59, 0x00, 0x3d, 0xe7, 0x5f, 0xd3, 0xca, 0x79, 0x92,
0xfd, 0xd1, 0xb0, 0xd4, 0x01,
],
},
Signature {
index: 6,
signature: [
0x51, 0xf0, 0x6a, 0xb6, 0xfd, 0xfd, 0x42, 0xf3, 0x23, 0x84, 0x0a, 0x81,
0x8b, 0x38, 0xcf, 0xd1, 0xc9, 0x9d, 0x22, 0x36, 0x6c, 0x87, 0x79, 0x03,
0xb9, 0x8d, 0xf4, 0x0b, 0xda, 0xf2, 0xa2, 0x04, 0x2e, 0xb8, 0xcd, 0x0e,
0x59, 0xc4, 0x63, 0x7e, 0x6a, 0x80, 0xa1, 0x99, 0xb1, 0x10, 0x8c, 0xd7,
0x65, 0x9b, 0xa8, 0x37, 0x0f, 0x6f, 0x94, 0x76, 0xb4, 0x79, 0x83, 0x98,
0x54, 0xd8, 0xc1, 0xa6, 0x00,
],
},
Signature {
index: 7,
signature: [
0x78, 0x4b, 0x37, 0xc6, 0x10, 0x3c, 0x75, 0x2c, 0xd9, 0x7a, 0x58, 0x6b,
0xe8, 0xe1, 0xce, 0xff, 0x22, 0xa6, 0xe4, 0x88, 0x43, 0x32, 0x84, 0xff,
0x31, 0xcd, 0x90, 0x8b, 0x5c, 0x7d, 0x87, 0xe2, 0x44, 0xda, 0x75, 0x16,
0xd0, 0x7a, 0x0f, 0xa6, 0x92, 0x68, 0x4f, 0x82, 0x6e, 0x5e, 0x6c, 0x8b,
0x45, 0x20, 0x04, 0x20, 0x6d, 0x6f, 0xe5, 0xba, 0xe4, 0x6a, 0xfb, 0x1c,
0x21, 0x8c, 0xf5, 0x0d, 0x00,
],
},
Signature {
index: 9,
signature: [
0x0b, 0x6c, 0xcc, 0xe6, 0x7e, 0xd5, 0x01, 0xcb, 0x20, 0xfb, 0x06, 0xde,
0x76, 0xb9, 0x08, 0x3f, 0x13, 0x29, 0xad, 0x78, 0xd3, 0x84, 0x51, 0x7a,
0x94, 0xab, 0x8e, 0x9a, 0xa6, 0x01, 0xbe, 0x7a, 0x0f, 0x00, 0xbb, 0x60,
0x3b, 0x36, 0x50, 0x4a, 0x74, 0xdd, 0xc3, 0x67, 0x35, 0x9c, 0x8e, 0xa0,
0x7f, 0x5c, 0xcd, 0x50, 0x22, 0x77, 0x2c, 0xc1, 0x57, 0xe0, 0x0f, 0x54,
0x15, 0x63, 0x71, 0xe4, 0x01,
],
},
Signature {
index: 11,
signature: [
0xe1, 0xe6, 0xb7, 0x12, 0xa2, 0x35, 0x3e, 0x49, 0x07, 0x92, 0x9c, 0x40,
0x57, 0xd5, 0xba, 0xbe, 0xf8, 0x0d, 0xc0, 0x9f, 0xeb, 0xb3, 0xdc, 0x29,
0x34, 0xf4, 0x40, 0x48, 0x79, 0x5e, 0xef, 0x95, 0x4e, 0x99, 0x10, 0x6c,
0xb2, 0x9b, 0x1a, 0x8d, 0x93, 0x53, 0x69, 0xda, 0xec, 0xbb, 0x8b, 0xae,
0x45, 0x19, 0x2e, 0xfc, 0x3c, 0xed, 0xbc, 0x57, 0x31, 0x5d, 0x67, 0xec,
0x9e, 0xa8, 0x00, 0x0a, 0x00,
],
},
Signature {
index: 13,
signature: [
0xdc, 0xc0, 0x4c, 0xd7, 0x55, 0x6a, 0x94, 0xe8, 0xb4, 0x18, 0xdc, 0x3a,
0x8a, 0x00, 0xef, 0xb1, 0x28, 0xd4, 0xf7, 0x6f, 0x71, 0x43, 0x12, 0x47,
0xa9, 0x2e, 0x7a, 0x19, 0xa8, 0x33, 0xfc, 0x02, 0x21, 0x5d, 0x1f, 0xd6,
0x9e, 0x0e, 0x59, 0x6d, 0xa5, 0x31, 0xea, 0x58, 0x7a, 0xe2, 0xac, 0x2f,
0x36, 0xd2, 0x0f, 0x3b, 0x19, 0x33, 0x06, 0x0a, 0xd6, 0xef, 0x3e, 0x9d,
0x7d, 0x84, 0x75, 0xba, 0x01,
],
},
Signature {
index: 14,
signature: [
0xc7, 0x62, 0x16, 0x49, 0x49, 0x06, 0xae, 0xd8, 0xd8, 0x31, 0x0c, 0x31,
0xa5, 0xe4, 0xa5, 0x45, 0xea, 0x2a, 0xf8, 0xdb, 0xe3, 0x8e, 0xeb, 0x74,
0xa3, 0xd1, 0x4f, 0x07, 0x34, 0xdd, 0x2b, 0x1a, 0x21, 0xb9, 0x85, 0x0e,
0xa2, 0x0e, 0x6c, 0x2c, 0xa4, 0x22, 0x48, 0x78, 0x8b, 0xfb, 0x17, 0x43,
0x9d, 0x37, 0xab, 0xda, 0x25, 0xd7, 0x05, 0xb5, 0x68, 0xb2, 0x65, 0xb1,
0x13, 0xb9, 0x76, 0xc5, 0x00,
],
},
Signature {
index: 16,
signature: [
0xc6, 0x1f, 0xe6, 0xeb, 0xea, 0x61, 0xf2, 0xca, 0xe8, 0x95, 0xc3, 0x34,
0x96, 0x50, 0x70, 0x48, 0x7d, 0x39, 0xab, 0x6b, 0xf0, 0x44, 0x28, 0x34,
0x0a, 0xf0, 0xf7, 0x69, 0x9f, 0xdd, 0xd3, 0xc0, 0x1e, 0x0c, 0x86, 0xda,
0xea, 0x9e, 0xb4, 0x49, 0x70, 0x12, 0x48, 0xa2, 0x4f, 0xa4, 0xc0, 0xff,
0x75, 0xfb, 0x60, 0x0b, 0x2c, 0x01, 0x34, 0xbd, 0x72, 0x17, 0x91, 0xf6,
0x67, 0x11, 0x6e, 0x42, 0x00,
],
},
Signature {
index: 17,
signature: [
0x19, 0xb7, 0x9c, 0xaa, 0x25, 0x0e, 0x75, 0xda, 0x14, 0x82, 0xc7, 0xf4,
0x12, 0x68, 0x73, 0x08, 0xd1, 0x3e, 0xf7, 0x00, 0xf7, 0x26, 0xb9, 0x94,
0x17, 0xbb, 0x75, 0xae, 0x6d, 0xd1, 0x43, 0xfc, 0x61, 0x9f, 0x8c, 0x9c,
0x29, 0xc7, 0x3f, 0x99, 0x49, 0xaf, 0xfd, 0x16, 0x29, 0xcc, 0x28, 0x6a,
0x61, 0x91, 0x7e, 0xd7, 0x85, 0x37, 0x54, 0xa4, 0x67, 0x02, 0x92, 0x0b,
0x76, 0xcf, 0x90, 0xeb, 0x00,
],
},
Signature {
index: 18,
signature: [
0xb0, 0xba, 0xb5, 0xe1, 0xa8, 0xb3, 0x21, 0xe9, 0x2f, 0x9d, 0x3d, 0xf9,
0x24, 0x30, 0x18, 0x3e, 0x48, 0x43, 0xe5, 0x7c, 0x6f, 0x8c, 0x15, 0xc2,
0x2d, 0x62, 0xb2, 0xf8, 0xe4, 0xb8, 0xcd, 0xc2, 0x75, 0x9f, 0x28, 0x54,
0xb8, 0xd1, 0x22, 0xac, 0xdb, 0x4b, 0x4d, 0x89, 0x28, 0xb8, 0x7d, 0xf4,
0x19, 0x9c, 0xc6, 0x83, 0x01, 0x1d, 0x2e, 0x9b, 0x7d, 0x41, 0xa9, 0x6d,
0x8b, 0x48, 0x0c, 0xcc, 0x01,
],
},
],
timestamp: 0,
nonce: 969_194_102,
emitter_chain: Chain::Solana,
emitter_address: GOVERNANCE_EMITTER,
sequence: 2_694_510_404_604_284_400,
consistency_level: 32,
payload: GovernancePacket {
chain: Chain::Terra,
action: Action::ContractUpgrade {
new_contract: Address([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x83,
]),
},
},
};
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
let encoded = serde_json::to_string(&vaa).unwrap();
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
}
}

View File

@ -9,335 +9,269 @@
//! includes parsers for the core VAA type. Programs targetting wormhole can use this module to
//! parse and verify incoming VAA's securely.
use nom::combinator::rest;
use nom::error::{Error, ErrorKind};
use nom::multi::{count, fill};
use nom::number::complete::{u16, u32, u64, u8};
use nom::number::Endianness;
use nom::{Err, Finish, IResult};
use std::convert::TryFrom;
use std::io::{self, Write};
use crate::{
require, Chain,
WormholeError::{
self, InvalidGovernanceAction, InvalidGovernanceChain, InvalidGovernanceModule,
},
};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use sha3::Digest as Sha3Digest;
// Import Module Specific VAAs.
pub mod core;
pub mod nft;
pub mod token;
use crate::{Address, Chain, GuardianAddress};
/// Signatures are typical ECDSA signatures prefixed with a Guardian position. These have the
/// following byte layout:
/// ```markdown
/// 0 .. 1: Guardian No.
/// 1 .. 65: Signature (ECDSA)
/// 65 .. 66: Recovery ID (ECDSA)
/// 0 .. 64: Signature (ECDSA)
/// 64 .. 65: Recovery ID (ECDSA)
/// ```
pub type Signature = [u8; 66];
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Signature {
pub index: u8,
#[serde(with = "crate::serde_array")]
pub signature: [u8; 65],
}
/// Wormhole specifies token addresses as 32 bytes. Addresses that are shorter, for example 20 byte
/// Ethereum addresses, are left zero padded to 32.
pub type ForeignAddress = [u8; 32];
/// Fields on VAA's are all usually fixed bytestrings, however they often contain UTF-8. When
/// parsed these result in `String` with the additional constraint that they are always equal or
/// less to the underlying byte field.
type ShortUTFString = String;
impl Default for Signature {
fn default() -> Self {
Self {
index: 0,
signature: [0; 65],
}
}
}
/// The core VAA itself. This structure is what is received by a contract on the receiving side of
/// a wormhole message passing flow. The payload of the message must be parsed separately to the
/// VAA itself as it is completely user defined.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct VAA {
// Header
/// a wormhole message passing flow. The generic parameter `P` represents the user-defined payload
/// for the VAA.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Vaa<P> {
// Implementation note: it would be nice if we could use `#[serde(flatten)]` and directly embed the
// `Header` and `Body` structs. Unfortunately using flatten causes serde to serialize/deserialize
// the struct as a map, which requires the underlying data format to encode field names on the
// wire, which the wormhole data format does not do. So instead we have to duplicate the fields
// and provide a conversion function to/from `Vaa` to `(Header, Body)`.
pub version: u8,
pub guardian_set_index: u32,
pub signatures: Vec<Signature>,
// Body
pub timestamp: u32,
pub nonce: u32,
pub emitter_chain: Chain,
pub emitter_address: ForeignAddress,
pub emitter_address: Address,
pub sequence: u64,
pub consistency_level: u8,
pub payload: Vec<u8>,
pub payload: P,
}
/// Contains the hash, secp256k1 payload, and serialized digest of the VAA. These are used in
/// various places in Wormhole codebases.
pub struct VAADigest {
pub digest: Vec<u8>,
/// The header for a VAA.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Header {
pub version: u8,
pub guardian_set_index: u32,
pub signatures: Vec<Signature>,
}
/// The body for a VAA.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Body<P> {
/// Seconds since UNIX epoch.
pub timestamp: u32,
pub nonce: u32,
pub emitter_chain: Chain,
pub emitter_address: Address,
pub sequence: u64,
pub consistency_level: u8,
pub payload: P,
}
/// Digest data for the Body.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Digest {
/// Guardians don't hash the VAA body directly, instead they hash the VAA and sign the hash. The
/// purpose of this is it means when submitting a VAA on-chain we only have to submit the hash
/// which reduces gas costs.
pub hash: [u8; 32],
/// The secp256k_hash is the hash of the hash of the VAA. The reason we provide this is because
/// of how secp256k works internally. It hashes its payload before signing. This means that
/// when verifying secp256k signatures, we're actually checking if a guardian has signed the
/// hash of the hash of the VAA. Functions such as `ecrecover` expect the secp256k hash rather
/// than the original payload.
pub secp256k_hash: [u8; 32],
}
impl VAA {
/// Given any argument treatable as a series of bytes, attempt to deserialize into a valid VAA.
pub fn from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, WormholeError> {
match parse_vaa(input.as_ref()).finish() {
Ok(input) => Ok(input.1),
Err(e) => Err(WormholeError::ParseError(e.code as usize)),
/// Calculates and returns the digest for `body` to be used in VAA operations.
///
/// A VAA is distinguished by the unique 256bit Keccak256 hash of its body. This hash is
/// utilised in all Wormhole components for identifying unique VAA's, including the bridge,
/// modules, and core guardian software. The `Digest` is documented with reasoning for
/// each field.
///
/// NOTE: This function uses a library to do Keccak256 hashing, but on-chain this may not be
/// efficient. If efficiency is needed, consider calling `body()` instead and hashing the
/// result using on-chain primitives.
pub fn digest(body: &[u8]) -> io::Result<Digest> {
// The `body` of the VAA is hashed to produce a `digest` of the VAA.
let hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
h.write_all(body)?;
h.finalize().into()
};
// Hash `hash` again to get the secp256k internal hash, see `Digest` for more details.
let secp256k_hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
h.write_all(&hash)?;
h.finalize().into()
};
Ok(Digest {
hash,
secp256k_hash,
})
}
impl<P> Vaa<P> {
/// Check if the VAA is a Governance VAA.
pub fn is_governance(&self) -> bool {
self.emitter_address == crate::GOVERNANCE_EMITTER && self.emitter_chain == Chain::Solana
}
}
impl<P> From<Vaa<P>> for (Header, Body<P>) {
fn from(v: Vaa<P>) -> Self {
(
Header {
version: v.version,
guardian_set_index: v.guardian_set_index,
signatures: v.signatures,
},
Body {
timestamp: v.timestamp,
nonce: v.nonce,
emitter_chain: v.emitter_chain,
emitter_address: v.emitter_address,
sequence: v.sequence,
consistency_level: v.consistency_level,
payload: v.payload,
},
)
}
}
impl<P> From<(Header, Body<P>)> for Vaa<P> {
fn from((hdr, body): (Header, Body<P>)) -> Self {
Vaa {
version: hdr.version,
guardian_set_index: hdr.guardian_set_index,
signatures: hdr.signatures,
timestamp: body.timestamp,
nonce: body.nonce,
emitter_chain: body.emitter_chain,
emitter_address: body.emitter_address,
sequence: body.sequence,
consistency_level: body.consistency_level,
payload: body.payload,
}
}
}
/// A VAA is distinguished by the unique hash of its deterministic components. This method
/// returns a 256 bit Keccak hash of these components. This hash is utilised in all Wormhole
/// components for identifying unique VAA's, including the bridge, modules, and core guardian
/// software.
pub fn digest(&self) -> Option<VAADigest> {
use byteorder::{BigEndian, WriteBytesExt};
use sha3::Digest;
use std::io::{Cursor, Write};
impl Header {
pub fn verify(&self, _body: &[u8], _addrs: &[GuardianAddress]) -> anyhow::Result<Digest> {
todo!("VAA body verification")
}
}
// Hash Deterministic Pieces
let body = {
let mut v = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(self.timestamp).ok()?;
v.write_u32::<BigEndian>(self.nonce).ok()?;
v.write_u16::<BigEndian>(self.emitter_chain.clone() as u16)
.ok()?;
let _ = v.write(&self.emitter_address).ok()?;
v.write_u64::<BigEndian>(self.sequence).ok()?;
v.write_u8(self.consistency_level).ok()?;
let _ = v.write(&self.payload).ok()?;
v.into_inner()
};
impl<P: Serialize> Body<P> {
/// Body Digest Components.
///
/// A VAA is distinguished by the unique 256bit Keccak256 hash of its body. This hash is
/// utilised in all Wormhole components for identifying unique VAA's, including the bridge,
/// modules, and core guardian software. The `Digest` is documented with reasoning for
/// each field.
///
/// NOTE: This function uses a library to do Keccak256 hashing, but on-chain this may not be
/// efficient. If efficiency is needed, consider calling `serde_wormhole::to_writer` instead
/// and hashing the result using on-chain primitives.
#[inline]
pub fn digest(&self) -> anyhow::Result<Digest> {
self.digest_with_payload(&[])
}
// We hash the body so that secp256k1 signatures are signing the hash instead of the body
// within our contracts. We do this so we don't have to submit the entire VAA for signature
// verification, only the hash.
/// Like `digest` but allows specifying an additional payload to include in the body hash.
pub fn digest_with_payload(&self, payload: &[u8]) -> anyhow::Result<Digest> {
// The `body` of the VAA is hashed to produce a `digest` of the VAA.
let hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
let _ = h.write(body.as_slice()).unwrap();
serde_wormhole::to_writer(&mut h, self).context("failed to serialize body")?;
h.write_all(payload)
.context("failed to hash extra payload")?;
h.finalize().into()
};
Some(VAADigest { digest: body, hash })
// Hash `hash` again to get the secp256k internal hash, see `Digest` for detail.
let secp256k_hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
h.write_all(&hash)
.context("failed to compute second hash")?;
h.finalize().into()
};
Ok(Digest {
hash,
secp256k_hash,
})
}
}
/// Using nom, parse a fixed array of bytes without any allocation. Useful for parsing addresses,
/// signatures, identifiers, etc.
#[inline]
pub fn parse_fixed<const S: usize>(input: &[u8]) -> IResult<&[u8], [u8; S]> {
let mut buffer = [0u8; S];
let (i, _) = fill(u8, &mut buffer)(input)?;
Ok((i, buffer))
}
/// Parse a Chain ID, which is a 16 bit numeric ID. The mapping of network to ID is defined by the
/// Wormhole standard.
#[inline]
pub fn parse_chain(input: &[u8]) -> IResult<&[u8], Chain> {
let (i, chain) = u16(Endianness::Big)(input)?;
let chain = Chain::try_from(chain).map_err(|_| Err::Error(Error::new(i, ErrorKind::NoneOf)))?;
Ok((i, chain))
}
/// Parse a VAA from a vector of raw bytes. Nom handles situations where the data is either too
/// short or too long.
#[inline]
fn parse_vaa(input: &[u8]) -> IResult<&[u8], VAA> {
let (i, version) = u8(input)?;
let (i, guardian_set_index) = u32(Endianness::Big)(i)?;
let (i, signature_count) = u8(i)?;
let (i, signatures) = count(parse_fixed, signature_count.into())(i)?;
let (i, timestamp) = u32(Endianness::Big)(i)?;
let (i, nonce) = u32(Endianness::Big)(i)?;
let (i, emitter_chain) = parse_chain(i)?;
let (i, emitter_address) = parse_fixed(i)?;
let (i, sequence) = u64(Endianness::Big)(i)?;
let (i, consistency_level) = u8(i)?;
let (i, payload) = rest(i)?;
Ok((
i,
VAA {
version,
guardian_set_index,
signatures,
timestamp,
nonce,
emitter_chain,
emitter_address,
sequence,
consistency_level,
payload: payload.to_vec(),
},
))
}
/// All current Wormhole programs using Governance are prefixed with a Governance header with a
/// consistent format.
pub struct GovHeader {
pub module: [u8; 32],
pub action: u8,
pub chains: Chain,
}
pub trait GovernanceAction: Sized {
const ACTION: u8;
const MODULE: &'static [u8];
/// Implement a nom parser for the Action.
fn parse(input: &[u8]) -> IResult<&[u8], Self>;
/// Serialize to Wormhole wire format.
/// fn serialize(&self) -> Result<Vec<u8>, WormholeError>;
/// Parses an Action from a governance payload securely.
fn from_bytes<T: AsRef<[u8]>>(
input: T,
chain: Option<Chain>,
) -> Result<(GovHeader, Self), WormholeError> {
match parse_action(input.as_ref()).finish() {
Ok((_, (header, action))) => {
// If no Chain is given, we assume All, which implies always valid.
let chain = chain.unwrap_or(Chain::All);
// Left 0-pad the MODULE in case it is unpadded.
let mut module = [0u8; 32];
let modlen = Self::MODULE.len();
module[32 - modlen..].copy_from_slice(Self::MODULE);
// Verify Governance Data.
let valid_chain = chain == header.chains || chain == Chain::All;
let valid_action = header.action == Self::ACTION;
let valid_module = module == header.module;
require!(valid_action, InvalidGovernanceAction);
require!(valid_chain, InvalidGovernanceChain);
require!(valid_module, InvalidGovernanceModule);
Ok((header, action))
}
Err(e) => Err(WormholeError::ParseError(e.code as usize)),
}
}
}
#[inline]
pub fn parse_action<A: GovernanceAction>(input: &[u8]) -> IResult<&[u8], (GovHeader, A)> {
let (i, header) = parse_governance_header(input)?;
let (i, action) = A::parse(i)?;
Ok((i, (header, action)))
}
#[inline]
pub fn parse_governance_header(input: &[u8]) -> IResult<&[u8], GovHeader> {
let (i, module) = parse_fixed(input)?;
let (i, action) = u8(i)?;
let (i, chains) = u16(Endianness::Big)(i)?;
Ok((
i,
GovHeader {
module,
action,
chains: Chain::try_from(chains).unwrap(),
},
))
}
#[cfg(test)]
mod testing {
use super::{parse_governance_header, Chain, VAA};
mod test {
use super::*;
#[test]
fn test_valid_gov_header() {
let module =
hex::decode("000000000000000000000000000000000000000000546f6b656e427269646765")
.unwrap();
fn arbitrary_payload() {
let buf = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xe2, 0x9d, 0x3a, 0xd1, 0x80, 0xb1, 0x53,
0xd6, 0x8c, 0x3f, 0x44, 0x5d, 0x75, 0xea, 0xa6, 0x2f, 0xcc, 0x99, 0x69, 0x09, 0x45,
0xba, 0xaf, 0x4a, 0xd0, 0x46, 0x3e, 0x9c, 0xe4, 0x4f, 0x27, 0xf7, 0x5d, 0xa3, 0xd4,
0x9f, 0x79, 0x72, 0x29, 0x20, 0xaa, 0xc8, 0x1b, 0xa2, 0xbe, 0x80, 0xf6, 0x88, 0x89,
0x5f, 0x17, 0x49, 0x42, 0xfe, 0xdc, 0x40, 0x3b, 0xc4, 0xe5, 0xce, 0x35, 0x55, 0xb7,
0x7b, 0x00, 0x62, 0xb9, 0xf7, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x9a, 0x2a, 0x01,
0xb7, 0x05, 0x19, 0xf6, 0x7a, 0xdb, 0x30, 0x9a, 0x99, 0x4e, 0xc8, 0xc6, 0x9a, 0x96,
0x7e, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x46, 0x72, 0x6f,
0x6d, 0x3a, 0x20, 0x65, 0x76, 0x6d, 0x30, 0x5c, 0x6e, 0x4d, 0x73, 0x67, 0x3a, 0x20,
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21,
];
// Decode VAA.
let vaa = hex::decode("01000000000100b072505b5b999c1d08905c02e2b6b2832ef72c0ba6c8db4f77fe457ef2b3d053410b1e92a9194d9210df24d987ac83d7b6f0c21ce90f8bc1869de0898bda7e980100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000013c1bfa00000000000000000000000000000000000000000000546f6b656e42726964676501000000013b26409f8aaded3f5ddca184695aa6a0fa829b0c85caf84856324896d214ca98").unwrap();
let vaa = VAA::from_bytes(vaa).unwrap();
// Decode Payload
let (_, header) = parse_governance_header(&vaa.payload).unwrap();
// Confirm Parsed matches Required.
assert_eq!(&header.module, &module[..]);
assert_eq!(header.action, 1);
assert_eq!(header.chains, Chain::All);
}
// Legacy VAA Signature Struct.
#[derive(Default, Clone)]
pub struct VAASignature {
pub signature: Vec<u8>,
pub guardian_index: u8,
}
// Original VAA Parsing Code. Used to compare current code to old for parity.
pub fn legacy_deserialize(data: &[u8]) -> std::result::Result<VAA, std::io::Error> {
use byteorder::{BigEndian, ReadBytesExt};
use std::convert::TryFrom;
use std::io::Read;
let mut rdr = std::io::Cursor::new(data);
let mut v = VAA {
..Default::default()
let vaa = Vaa {
version: 1,
guardian_set_index: 0,
signatures: vec![Signature {
index: 0,
signature: [
0xe2, 0x9d, 0x3a, 0xd1, 0x80, 0xb1, 0x53, 0xd6, 0x8c, 0x3f, 0x44, 0x5d, 0x75,
0xea, 0xa6, 0x2f, 0xcc, 0x99, 0x69, 0x09, 0x45, 0xba, 0xaf, 0x4a, 0xd0, 0x46,
0x3e, 0x9c, 0xe4, 0x4f, 0x27, 0xf7, 0x5d, 0xa3, 0xd4, 0x9f, 0x79, 0x72, 0x29,
0x20, 0xaa, 0xc8, 0x1b, 0xa2, 0xbe, 0x80, 0xf6, 0x88, 0x89, 0x5f, 0x17, 0x49,
0x42, 0xfe, 0xdc, 0x40, 0x3b, 0xc4, 0xe5, 0xce, 0x35, 0x55, 0xb7, 0x7b, 0x00,
],
}],
timestamp: 1_656_354_705,
nonce: 0,
emitter_chain: Chain::Ethereum,
emitter_address: Address([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x9a,
0x2a, 0x01, 0xb7, 0x05, 0x19, 0xf6, 0x7a, 0xdb, 0x30, 0x9a, 0x99, 0x4e, 0xc8, 0xc6,
0x9a, 0x96, 0x7e, 0x8b,
]),
sequence: 0,
consistency_level: 1,
payload: (),
};
v.version = rdr.read_u8()?;
v.guardian_set_index = rdr.read_u32::<BigEndian>()?;
let len_sig = rdr.read_u8()?;
let mut sigs: Vec<_> = Vec::with_capacity(len_sig as usize);
for _i in 0..len_sig {
let mut sig = [0u8; 66];
sig[0] = rdr.read_u8()?;
rdr.read_exact(&mut sig[1..66])?;
sigs.push(sig);
}
v.signatures = sigs;
v.timestamp = rdr.read_u32::<BigEndian>()?;
v.nonce = rdr.read_u32::<BigEndian>()?;
v.emitter_chain = Chain::try_from(rdr.read_u16::<BigEndian>()?).unwrap();
let mut emitter_address = [0u8; 32];
rdr.read_exact(&mut emitter_address)?;
v.emitter_address = emitter_address;
v.sequence = rdr.read_u64::<BigEndian>()?;
v.consistency_level = rdr.read_u8()?;
let _ = rdr.read_to_end(&mut v.payload)?;
Ok(v)
let (actual, payload) = serde_wormhole::from_slice_with_payload(&buf).unwrap();
assert_eq!(vaa, actual);
assert_eq!(bstr::B("From: evm0\\nMsg: Hello World!"), payload);
assert_eq!(&buf[..123], &serde_wormhole::to_vec(&vaa).unwrap());
}
#[test]
fn test_parse_vaa_parity() {
// Decode VAA with old and new parsers, and compare result.
let vaa = hex::decode("01000000000100b072505b5b999c1d08905c02e2b6b2832ef72c0ba6c8db4f77fe457ef2b3d053410b1e92a9194d9210df24d987ac83d7b6f0c21ce90f8bc1869de0898bda7e980100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000013c1bfa00000000000000000000000000000000000000000000546f6b656e42726964676501000000013b26409f8aaded3f5ddca184695aa6a0fa829b0c85caf84856324896d214ca98").unwrap();
let new = VAA::from_bytes(&vaa).unwrap();
let old = legacy_deserialize(&vaa).unwrap();
assert_eq!(new, old);
}
#[test]
fn test_valid_parse_vaa() {
let signers = hex::decode("00b072505b5b999c1d08905c02e2b6b2832ef72c0ba6c8db4f77fe457ef2b3d053410b1e92a9194d9210df24d987ac83d7b6f0c21ce90f8bc1869de0898bda7e9801").unwrap();
let payload = hex::decode("000000000000000000000000000000000000000000546f6b656e42726964676501000000013b26409f8aaded3f5ddca184695aa6a0fa829b0c85caf84856324896d214ca98").unwrap();
let emitter =
hex::decode("0000000000000000000000000000000000000000000000000000000000000004")
.unwrap();
// Decode VAA.
let vaa = hex::decode("01000000000100b072505b5b999c1d08905c02e2b6b2832ef72c0ba6c8db4f77fe457ef2b3d053410b1e92a9194d9210df24d987ac83d7b6f0c21ce90f8bc1869de0898bda7e980100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000013c1bfa00000000000000000000000000000000000000000000546f6b656e42726964676501000000013b26409f8aaded3f5ddca184695aa6a0fa829b0c85caf84856324896d214ca98").unwrap();
let vaa = VAA::from_bytes(vaa).unwrap();
// Verify Decoded VAA.
assert_eq!(vaa.version, 1);
assert_eq!(vaa.guardian_set_index, 0);
assert_eq!(vaa.signatures.len(), 1);
assert_eq!(vaa.signatures[0][..], signers);
assert_eq!(vaa.timestamp, 1);
assert_eq!(vaa.nonce, 1);
assert_eq!(vaa.emitter_chain, Chain::Solana);
assert_eq!(vaa.emitter_address, emitter[..]);
assert_eq!(vaa.sequence, 20_716_538);
assert_eq!(vaa.consistency_level, 0);
assert_eq!(vaa.payload, payload);
}
#[test]
fn test_invalid_vaa() {}
}

View File

@ -1,90 +0,0 @@
//! This module exposes parsers for core bridge VAAs. The main job of the bridge is to forward
//! VAA's to other chains, however governance actions are themselves VAAs and as such the bridge
//! requires parsing Bridge specific VAAs.
//!
//! The core bridge does not define any general VAA's, thus all the payloads in this file are
//! expected to require governance to be executed.
use nom::multi::{count, fill};
use nom::number::complete::{u32, u8};
use nom::number::Endianness;
use nom::IResult;
use primitive_types::U256;
use crate::vaa::{parse_fixed, GovernanceAction};
pub struct GovernanceContractUpgrade {
pub new_contract: [u8; 32],
}
impl GovernanceAction for GovernanceContractUpgrade {
const MODULE: &'static [u8] = b"Core";
const ACTION: u8 = 1;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (i, new_contract) = parse_fixed(input)?;
Ok((i, Self { new_contract }))
}
}
pub struct GovernanceGuardianSetChange {
pub new_guardian_set_index: u32,
pub new_guardian_set: Vec<[u8; 20]>,
}
impl GovernanceAction for GovernanceGuardianSetChange {
const MODULE: &'static [u8] = b"Core";
const ACTION: u8 = 2;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (i, new_guardian_set_index) = u32(Endianness::Big)(input)?;
let (i, guardian_count) = u8(i)?;
let (i, new_guardian_set) = count(parse_fixed, guardian_count.into())(i)?;
Ok((
i,
Self {
new_guardian_set_index,
new_guardian_set,
},
))
}
}
pub struct GovernanceSetMessageFee {
pub fee: U256,
}
impl GovernanceAction for GovernanceSetMessageFee {
const MODULE: &'static [u8] = b"Core";
const ACTION: u8 = 3;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let mut fee = [0u8; 32];
let (i, _) = fill(u8, &mut fee)(input)?;
Ok((
i,
Self {
fee: U256::from_big_endian(&fee),
},
))
}
}
pub struct GovernanceTransferFees {
pub amount: U256,
pub to: [u8; 32],
}
impl GovernanceAction for GovernanceTransferFees {
const MODULE: &'static [u8] = b"Core";
const ACTION: u8 = 4;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let mut amount = [0u8; 32];
let (i, _) = fill(u8, &mut amount)(input)?;
let (i, to) = parse_fixed(i)?;
Ok((
i,
Self {
amount: U256::from_big_endian(&amount),
to,
},
))
}
}

View File

@ -1,124 +0,0 @@
//! This module exposes parsers for NFT bridge VAAs. Token bridging relies on VAA's that indicate
//! custody/lockup/burn events in order to maintain token parity between multiple chains. These
//! parsers can be used to read these VAAs. It also defines the Governance actions that this module
//! supports, namely contract upgrades and chain registrations.
use nom::bytes::complete::take;
use nom::combinator::verify;
use nom::number::complete::u8;
use nom::{Finish, IResult};
use primitive_types::U256;
use std::str::from_utf8;
use crate::vaa::ShortUTFString;
use crate::vaa::{parse_chain, parse_fixed, GovernanceAction};
use crate::{parse_fixed_utf8, Chain, WormholeError};
/// Transfer is a message containing specifics detailing a token lock up on a sending chain. Chains
/// that are attempting to initiate a transfer must lock up tokens in some manner, such as in a
/// custody account or via burning, before emitting this message.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Transfer {
/// Address of the token. Left-zero-padded if shorter than 32 bytes
pub nft_address: [u8; 32],
/// Chain ID of the token
pub nft_chain: Chain,
/// Symbol of the token
pub symbol: ShortUTFString,
/// Name of the token
pub name: ShortUTFString,
/// TokenID of the token (big-endian uint256)
pub token_id: U256,
/// URI of the token metadata
pub uri: ShortUTFString,
/// Address of the recipient. Left-zero-padded if shorter than 32 bytes
pub to: [u8; 32],
/// Chain ID of the recipient
pub to_chain: Chain,
}
impl Transfer {
pub fn from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, WormholeError> {
match parse_payload_transfer(input.as_ref()).finish() {
Ok(input) => Ok(input.1),
Err(e) => Err(WormholeError::ParseError(e.code as usize)),
}
}
}
fn parse_payload_transfer(input: &[u8]) -> IResult<&[u8], Transfer> {
// Parse Payload
let (i, _) = verify(u8, |&s| s == 0x1)(input)?;
let (i, nft_address) = parse_fixed(i)?;
let (i, nft_chain) = parse_chain(i)?;
let (i, symbol): (_, [u8; 32]) = parse_fixed(i)?;
let (i, name): (_, [u8; 32]) = parse_fixed(i)?;
let (i, token_id): (_, [u8; 32]) = parse_fixed(i)?;
let (i, uri_len) = u8(i)?;
let (i, uri) = take(uri_len)(i)?;
let (i, to) = parse_fixed(i)?;
let (i, to_chain) = parse_chain(i)?;
// Name/Symbol and URI should be UTF-8 strings, attempt to parse the first two by removing
// invalid bytes -- for the latter, assume UTF-8 and fail if unparseable.
let name = parse_fixed_utf8::<_, 32>(name).unwrap();
let symbol = parse_fixed_utf8::<_, 32>(symbol).unwrap();
let uri = from_utf8(uri).unwrap().to_string();
Ok((
i,
Transfer {
nft_address,
nft_chain,
symbol,
name,
token_id: U256::from_big_endian(&token_id),
uri,
to,
to_chain,
},
))
}
#[derive(PartialEq, Eq, Debug)]
pub struct GovernanceRegisterChain {
pub emitter: Chain,
pub endpoint_address: [u8; 32],
}
impl GovernanceAction for GovernanceRegisterChain {
const MODULE: &'static [u8] = b"NFTBridge";
const ACTION: u8 = 1;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (i, emitter) = parse_chain(input)?;
let (i, endpoint_address) = parse_fixed(i)?;
Ok((
i,
Self {
emitter,
endpoint_address,
},
))
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct GovernanceContractUpgrade {
pub new_contract: [u8; 32],
}
impl GovernanceAction for GovernanceContractUpgrade {
const MODULE: &'static [u8] = b"NFTBridge";
const ACTION: u8 = 2;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (i, new_contract) = parse_fixed(input)?;
Ok((i, Self { new_contract }))
}
}

View File

@ -1,161 +0,0 @@
//! This module exposes parsers for token bridge VAAs. Token bridging relies on VAA's that indicate
//! custody/lockup/burn events in order to maintain token parity between multiple chains. These
//! parsers can be used to read these VAAs. It also defines the Governance actions that this module
//! supports, namely contract upgrades and chain registrations.
use nom::combinator::verify;
use nom::multi::fill;
use nom::number::complete::u8;
use nom::{Finish, IResult};
use primitive_types::U256;
use crate::vaa::{parse_chain, parse_fixed, GovernanceAction, ShortUTFString};
use crate::{parse_fixed_utf8, Chain, WormholeError};
/// Transfer is a message containing specifics detailing a token lock up on a sending chain. Chains
/// that are attempting to initiate a transfer must lock up tokens in some manner, such as in a
/// custody account or via burning, before emitting this message.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Transfer {
/// Amount being transferred (big-endian uint256)
pub amount: U256,
/// Address of the token. Left-zero-padded if shorter than 32 bytes
pub token_address: [u8; 32],
/// Chain ID of the token
pub token_chain: Chain,
/// Address of the recipient. Left-zero-padded if shorter than 32 bytes
pub to: [u8; 32],
/// Chain ID of the recipient
pub to_chain: Chain,
/// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount.
pub fee: U256,
}
impl Transfer {
pub fn from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, WormholeError> {
match parse_payload_transfer(input.as_ref()).finish() {
Ok(input) => Ok(input.1),
Err(e) => Err(WormholeError::ParseError(e.code as usize)),
}
}
}
fn parse_payload_transfer(input: &[u8]) -> IResult<&[u8], Transfer> {
// Parser Buffers.
let mut amount = [0u8; 32];
let mut fee = [0u8; 32];
// Parse Payload.
let (i, _) = verify(u8, |&s| s == 0x1)(input)?;
let (i, _) = fill(u8, &mut amount)(i)?;
let (i, token_address) = parse_fixed(i)?;
let (i, token_chain) = parse_chain(i)?;
let (i, to) = parse_fixed(i)?;
let (i, to_chain) = parse_chain(i)?;
let (i, _) = fill(u8, &mut fee)(i)?;
Ok((
i,
Transfer {
amount: U256::from_big_endian(&amount),
token_address,
token_chain,
to,
to_chain,
fee: U256::from_big_endian(&fee),
},
))
}
#[derive(PartialEq, Eq, Debug)]
pub struct AssetMeta {
/// Address of the original token on the source chain.
pub token_address: [u8; 32],
/// Source Chain ID.
pub token_chain: Chain,
/// Number of decimals the source token has on its origin chain.
pub decimals: u8,
/// Ticker Symbol for the token on its origin chain.
pub symbol: ShortUTFString,
/// Full Token name for the token on its origin chain.
pub name: ShortUTFString,
}
impl AssetMeta {
pub fn from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, WormholeError> {
match parse_payload_asset_meta(input.as_ref()).finish() {
Ok(input) => Ok(input.1),
Err(e) => Err(WormholeError::ParseError(e.code as usize)),
}
}
}
fn parse_payload_asset_meta(input: &[u8]) -> IResult<&[u8], AssetMeta> {
// Parse Payload.
let (i, _) = verify(u8, |&s| s == 0x2)(input)?;
let (i, token_address) = parse_fixed(i)?;
let (i, token_chain) = parse_chain(i)?;
let (i, decimals) = u8(i)?;
let (i, symbol): (_, [u8; 32]) = parse_fixed(i)?;
let (i, name): (_, [u8; 32]) = parse_fixed(i)?;
// Name/Symbol should be UTF-8 strings, attempt to parse them by removing invalid bytes.
let symbol = parse_fixed_utf8::<_, 32>(symbol).unwrap();
let name = parse_fixed_utf8::<_, 32>(name).unwrap();
Ok((
i,
AssetMeta {
token_address,
token_chain,
decimals,
symbol,
name,
},
))
}
#[derive(PartialEq, Eq, Debug)]
pub struct GovernanceRegisterChain {
pub emitter: Chain,
pub endpoint_address: [u8; 32],
}
impl GovernanceAction for GovernanceRegisterChain {
const MODULE: &'static [u8] = b"TokenBridge";
const ACTION: u8 = 1;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (i, emitter) = parse_chain(input)?;
let (i, endpoint_address) = parse_fixed(i)?;
Ok((
i,
Self {
emitter,
endpoint_address,
},
))
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct GovernanceContractUpgrade {
pub new_contract: [u8; 32],
}
impl GovernanceAction for GovernanceContractUpgrade {
const MODULE: &'static [u8] = b"TokenBridge";
const ACTION: u8 = 2;
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (i, new_contract) = parse_fixed(input)?;
Ok((i, Self { new_contract }))
}
}

View File

@ -1,65 +0,0 @@
[package]
name = "wormhole-sdk"
version = "0.1.0"
edition = "2018"
[features]
# Helper methods will target the Wormhole mainnet contract addresses.
mainnet = []
# Helper methosd will target the Wormhole testnet contract addresses.
testnet = []
# Helper methosd will target the Wormhole devnet contract addresses.
devnet = []
# Enable Optional dependencies that are only required when targetting Terra.
terra = [
"cosmwasm-std",
"cosmwasm-storage",
"schemars",
"serde",
"wormhole-bridge-terra",
]
# Enable Optional dependencies that are only required when targetting Solana.
solana = [
"solana-program",
"wormhole-bridge-solana",
]
[profile.release]
opt-level = 3
lto = "thin"
[dependencies]
borsh = { version="=0.9.3" }
nom = { version="7", default-features=false, features=["alloc"] }
primitive-types = { version = "0.9.0", default-features = false }
wormhole-core = { path="../core", version="0.1.0" }
# Solana Specific
solana-program = { version="=1.10.31", optional=true }
# Terra Specific
cosmwasm-std = { version = "0.16.0", optional=true }
cosmwasm-storage = { version = "0.16.0", optional=true }
schemars = { version = "0.8.1", optional=true }
serde = { version = "1.0.103", default-features = false, features = ["derive"], optional=true }
[dependencies.wormhole-bridge-solana]
path = "../../../solana/bridge/program"
version = "0.1.0"
optional = true
features = [ "no-entrypoint" ]
[dependencies.wormhole-bridge-terra]
path = "../../../terra/contracts/wormhole"
version = "0.1.0"
optional = true
features = [ "library" ]
[dev-dependencies]
byteorder = "*"
hex = "*"

View File

@ -1,7 +0,0 @@
[build]
rustflags = [
"-Cpasses=sancov-module",
"-Cllvm-args=-sanitizer-coverage-level=3",
"-Cllvm-args=-sanitizer-coverage-inline-8bit-counters",
"-Zsanitizer=address",
]

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
[package]
name = "wormhole-sdk-fuzz"
version = "0.0.0"
edition = "2021"
publish = false
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
[dependencies.wormhole-sdk]
path = ".."
features = ["solana", "vaa"]
# Create isolated workspace.
[workspace]
members = ["."]
[[bin]]
name = "vaa"
path = "fuzzers/vaa.rs"
[[bin]]
name = "governance"
path = "fuzzers/governance.rs"

View File

@ -1,7 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use wormhole_sdk::VAA;
fuzz_target!(|data: &[u8]| {
VAA::from_bytes(data);
});

View File

@ -1,7 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use wormhole_sdk::vaa::VAA;
fuzz_target!(|data: &[u8]| {
VAA::from_bytes(data);
});

View File

@ -1,12 +0,0 @@
//! Exposes an API implementation depending on which feature flags have been toggled for the
//! library. Check submodules for chain runtime specific documentation.
#[cfg(feature = "solana")]
pub mod solana;
#[cfg(feature = "solana")]
pub use solana::*;
#[cfg(feature = "terra")]
pub mod terra;
#[cfg(feature = "terra")]
pub use terra::*;

View File

@ -1,135 +0,0 @@
use borsh::BorshDeserialize;
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::program::invoke_signed;
use solana_program::pubkey::Pubkey;
use std::str::FromStr;
// Export Bridge API
pub use bridge::instructions;
pub use bridge::solitaire as bridge_entrypoint;
pub use bridge::types::ConsistencyLevel;
pub use bridge::BridgeConfig;
pub use bridge::BridgeData;
pub use bridge::MessageData;
pub use bridge::PostVAAData;
pub use bridge::PostedVAAData;
pub use bridge::VerifySignaturesData;
use wormhole_core::WormholeError;
use wormhole_core::VAA;
/// Export Core Mainnet Contract Address
#[cfg(feature = "mainnet")]
pub fn id() -> Pubkey {
Pubkey::from_str("worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth").unwrap()
}
/// Export Core Devnet Contract Address
#[cfg(feature = "testnet")]
pub fn id() -> Pubkey {
Pubkey::from_str("3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5").unwrap()
}
/// Export Local Tilt Devnet Contract Address
#[cfg(feature = "devnet")]
pub fn id() -> Pubkey {
Pubkey::from_str("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o").unwrap()
}
/// Derives the Wormhole configuration account address.
pub fn config(id: &Pubkey) -> Pubkey {
let (config, _) = Pubkey::find_program_address(&[b"Bridge"], &id);
config
}
/// Derives the Wormhole fee account address, users of the bridge must pay this address before
/// submitting messages to the bridge.
pub fn fee_collector(id: &Pubkey) -> Pubkey {
let (fee_collector, _) = Pubkey::find_program_address(&[b"fee_collector"], &id);
fee_collector
}
/// Derives the sequence address for an emitter, which is incremented after each message post.
pub fn sequence(id: &Pubkey, emitter: &Pubkey) -> Pubkey {
let (sequence, _) = Pubkey::find_program_address(&[b"Sequence", &emitter.to_bytes()], &id);
sequence
}
/// Derives the emitter address for a Solana contract, the emitter on Solana must be a signer, this
/// function helps generate a PDA and bump seed so users can emit using a PDA as the emitter.
pub fn emitter(id: &Pubkey) -> (Pubkey, Vec<&[u8]>, u8) {
let seeds = &["emitter".as_bytes()];
let (emitter, bump) = Pubkey::find_program_address(seeds, id);
(emitter, seeds.to_vec(), bump)
}
/// Deserialize helper the BridgeConfig from a Wormhole config account.
pub fn read_config(config: &AccountInfo) -> Result<BridgeConfig, WormholeError> {
let bridge_data = BridgeData::try_from_slice(&config.data.borrow())
.map_err(|_| WormholeError::DeserializeFailed)?;
Ok(bridge_data.config)
}
/// Deserialize helper for parsing from Borsh encoded VAA's from Solana accounts.
pub fn read_vaa(vaa: &AccountInfo) -> Result<PostedVAAData, WormholeError> {
Ok(PostedVAAData::try_from_slice(&vaa.data.borrow())
.map_err(|_| WormholeError::DeserializeFailed)?)
}
/// This helper method wraps the steps required to invoke Wormhole, it takes care of fee payment,
/// emitter derivation, and function invocation. This will be the right thing to use if you need to
/// simply emit a message in the most straight forward way possible.
pub fn post_message(
program_id: Pubkey,
payer: Pubkey,
message: Pubkey,
payload: impl AsRef<[u8]>,
consistency: ConsistencyLevel,
pda_seeds: Option<&[&[&[u8]]]>,
accounts: &[AccountInfo],
nonce: u32,
) -> ProgramResult {
// Derive any necessary Pubkeys, derivation makes sure that we match the accounts that are being
// provided by the user as well.
let id = id();
let fee_collector = fee_collector(&id);
let (emitter, mut emitter_seeds, bump) = emitter(&program_id);
let bump = &[bump];
emitter_seeds.push(bump);
// Filter for the Config AccountInfo so we can access its data.
let config = config(&id);
let config = accounts.iter().find(|item| *item.key == config).unwrap();
let config = read_config(config).unwrap();
let mut seeds = vec![&*emitter_seeds];
if let Some(v) = pda_seeds {
seeds.extend(v);
}
// Pay Fee to the Wormhole
invoke_signed(
&solana_program::system_instruction::transfer(&payer, &fee_collector, config.fee),
accounts,
&[],
)?;
// Invoke the Wormhole post_message endpoint to create an on-chain message.
invoke_signed(
&instructions::post_message(
id,
payer,
emitter,
message,
nonce,
payload.as_ref().to_vec(),
consistency,
)
.unwrap(),
accounts,
&seeds,
)?;
Ok(())
}

View File

@ -1,48 +0,0 @@
use cosmwasm_std::{
to_binary, Addr, Binary, CosmosMsg, DepsMut, Env, QueryRequest, StdResult, WasmMsg, WasmQuery,
};
use serde::Serialize;
use wormhole::msg::{ExecuteMsg, QueryMsg};
use wormhole::state::ParsedVAA;
/// Export Core Mainnet Contract Address
#[cfg(feature = "mainnet")]
pub fn id() -> Addr {
Addr::unchecked("terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5")
}
/// Export Core Devnet Contract Address
#[cfg(feature = "testnet")]
pub fn id() -> Addr {
Addr::unchecked("terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v")
}
/// Export Core Devnet Contract Address
#[cfg(feature = "devnet")]
pub fn id() -> Addr {
Addr::unchecked("terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5")
}
pub fn post_message(nonce: u32, message: impl AsRef<[u8]>) -> StdResult<CosmosMsg> {
Ok(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: id().to_string(),
funds: vec![],
msg: to_binary(&ExecuteMsg::PostMessage {
message: Binary::from(message.as_ref()),
nonce,
})?,
}))
}
/// Parse a VAA using the Wormhole contract Query interface.
pub fn parse_vaa(deps: DepsMut, env: Env, data: &Binary) -> StdResult<ParsedVAA> {
let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: id().to_string(),
msg: to_binary(&QueryMsg::VerifyVAA {
vaa: data.clone(),
block_time: env.block.time.seconds(),
})?,
}))?;
Ok(vaa)
}

View File

@ -1,21 +0,0 @@
//! This SDK provides API's for implementing cross-chain message passing via the Wormhole protocol.
//! This package aims to provide a consistent API regardless of the underlying runtime, but some
//! types will differ depending on which implementation is being targeted.
//!
//! Each implementation can be toggled using feature flags, which will switch out the underlying
//! depenencies to pull in the depenendices for the corresponding runtimes.
//!
//! Implementations:
//!
//! Runtime | Feature Flag | Version
//! ----------|-------------------------|----------------------------------------------------
//! Solana | --feature=solana | solana-sdk 1.7.1
//! Terra | --feature=terra | cosmos-sdk 0.16.0
//!
//! Docs specific to each blockchain's runtime can be found in submodules within the chains module
//! at the root of this package.
pub mod chains;
pub use chains::*;
pub use wormhole_core::*;