sdk: add core types for wormhole projects

Change-Id: Iabaebb7b4319d5b759044896b2b396d39bda33d7
This commit is contained in:
Reisen 2021-12-08 14:21:04 +00:00 committed by Reisen
parent d21f08d2cb
commit 97566d878a
9 changed files with 905 additions and 0 deletions

4
sdk/rust/Cargo.toml Normal file
View File

@ -0,0 +1,4 @@
[workspace]
members = [
"core",
]

26
sdk/rust/core/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "wormhole-core"
version = "0.1.0"
edition = "2018"
[features]
[profile.release]
opt-level = 3
lto = "thin"
[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 = "*"
[dev-dependencies]
byteorder = "*"
hex = "*"

View File

@ -0,0 +1,42 @@
//! 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
/// 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)]
pub enum Chain {
All = 0,
Solana = 1,
Ethereum = 2,
Terra = 3,
Binance = 4,
Polygon = 5,
AVAX = 6,
Oasis = 7,
}
impl TryFrom<u16> for Chain {
type Error = ();
fn try_from(other: u16) -> Result<Chain, Self::Error> {
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(()),
}
}
}
impl Default for Chain {
fn default() -> Self {
Self::All
}
}

View File

@ -0,0 +1,22 @@
/// 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),
}

36
sdk/rust/core/src/lib.rs Normal file
View File

@ -0,0 +1,36 @@
#![deny(unused_results)]
pub use chain::*;
pub use error::*;
pub use vaa::*;
pub mod chain;
pub mod vaa;
#[macro_use]
pub mod error;
/// 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;
// 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);
// Attempt UTF-8 Decoding. Stripping invalid Unicode characters (0xFFFD).
let mut buffer: Vec<char> = buffer.chars().collect();
buffer.retain(|&c| c != '\u{FFFD}');
Some(buffer.iter().collect())
}

370
sdk/rust/core/src/vaa.rs Normal file
View File

@ -0,0 +1,370 @@
//! VAA's represent a collection of signatures combined with a message and its metadata. VAA's are
//! used as a form of proof; by submitting a VAA to a target contract, the receiving contract can
//! make assumptions about the validity of state on the source chain.
//!
//! Wormhole defines several VAA's for use within Token/NFT bridge implemenetations, as well as
//! governance specific VAA's used within Wormhole's guardian network.
//!
//! This module provides definitions and parsers for all current Wormhole standard VAA's, and
//! 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::str::FromStr; // Remove in 2021
use crate::WormholeError::{
InvalidGovernanceAction,
InvalidGovernanceChain,
InvalidGovernanceModule,
};
use crate::{
require,
Chain,
WormholeError,
};
// Import Module Specific VAAs.
pub mod core;
pub mod nft;
pub mod token;
/// 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)
/// ```
pub type Signature = [u8; 66];
/// 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];
/// 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)]
pub struct VAA {
// Header
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 sequence: u64,
pub consistency_level: u8,
pub payload: Vec<u8>,
}
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)),
}
}
/// 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<[u8; 32]> {
use byteorder::{
BigEndian,
WriteBytesExt,
};
use sha3::Digest;
use std::io::{
Cursor,
Write,
};
// 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()
};
// 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.
let body: [u8; 32] = {
let mut h = sha3::Keccak256::default();
let _ = h.write(body.as_slice()).unwrap();
h.finalize().into()
};
Some(body)
}
}
/// 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();
(&mut 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.as_ref())?;
let (i, action) = A::parse(i)?;
Ok((i, (header, action)))
}
#[inline]
pub fn parse_governance_header<'i, 'a>(input: &'i [u8]) -> IResult<&'i [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,
};
#[test]
fn test_valid_gov_header() {
let signers = hex::decode("00b072505b5b999c1d08905c02e2b6b2832ef72c0ba6c8db4f77fe457ef2b3d053410b1e92a9194d9210df24d987ac83d7b6f0c21ce90f8bc1869de0898bda7e9801").unwrap();
let payload = hex::decode("000000000000000000000000000000000000000000546f6b656e42726964676501000000013b26409f8aaded3f5ddca184695aa6a0fa829b0c85caf84856324896d214ca98").unwrap();
let emitter =
hex::decode("0000000000000000000000000000000000000000000000000000000000000004")
.unwrap();
let module =
hex::decode("000000000000000000000000000000000000000000546f6b656e427269646765")
.unwrap();
// 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()
};
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)
}
#[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

@ -0,0 +1,99 @@
//! 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

@ -0,0 +1,134 @@
//! 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::{
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, 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: String,
/// Name of the token
pub name: String,
/// TokenID of the token (big-endian uint256)
pub token_id: U256,
/// URI of the token metadata
pub uri: String,
/// 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.as_ref())?;
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, 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, 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

@ -0,0 +1,172 @@
//! 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,
};
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, 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, 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: String,
/// Full token name for the token on its origin chain.
pub name: String,
}
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.as_ref())?;
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, 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, 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 }))
}
}