diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml new file mode 100644 index 000000000..ab265b0c6 --- /dev/null +++ b/sdk/rust/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +members = [ + "core", +] diff --git a/sdk/rust/core/Cargo.toml b/sdk/rust/core/Cargo.toml new file mode 100644 index 000000000..391ad0472 --- /dev/null +++ b/sdk/rust/core/Cargo.toml @@ -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 = "*" diff --git a/sdk/rust/core/src/chain.rs b/sdk/rust/core/src/chain.rs new file mode 100644 index 000000000..d9e255a5f --- /dev/null +++ b/sdk/rust/core/src/chain.rs @@ -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 for Chain { + type Error = (); + fn try_from(other: u16) -> Result { + 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 + } +} diff --git a/sdk/rust/core/src/error.rs b/sdk/rust/core/src/error.rs new file mode 100644 index 000000000..d6f3213fc --- /dev/null +++ b/sdk/rust/core/src/error.rs @@ -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), +} diff --git a/sdk/rust/core/src/lib.rs b/sdk/rust/core/src/lib.rs new file mode 100644 index 000000000..ea3cb74c1 --- /dev/null +++ b/sdk/rust/core/src/lib.rs @@ -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, const N: usize>(s: T) -> Option { + 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 = buffer.chars().collect(); + buffer.retain(|&c| c != '\u{FFFD}'); + + Some(buffer.iter().collect()) +} diff --git a/sdk/rust/core/src/vaa.rs b/sdk/rust/core/src/vaa.rs new file mode 100644 index 000000000..da541e513 --- /dev/null +++ b/sdk/rust/core/src/vaa.rs @@ -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, + + // 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, +} + +impl VAA { + /// Given any argument treatable as a series of bytes, attempt to deserialize into a valid VAA. + pub fn from_bytes>(input: T) -> Result { + 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::(self.timestamp).ok()?; + v.write_u32::(self.nonce).ok()?; + v.write_u16::(self.emitter_chain.clone() as u16).ok()?; + let _ = v.write(&self.emitter_address).ok()?; + v.write_u64::(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(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, WormholeError>; + + /// Parses an Action from a governance payload securely. + fn from_bytes>( + input: T, + chain: Option, + ) -> 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(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, + 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 { + 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::()?; + 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::()?; + v.nonce = rdr.read_u32::()?; + v.emitter_chain = Chain::try_from(rdr.read_u16::()?).unwrap(); + let mut emitter_address = [0u8; 32]; + rdr.read_exact(&mut emitter_address)?; + v.emitter_address = emitter_address; + v.sequence = rdr.read_u64::()?; + 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() { + } +} diff --git a/sdk/rust/core/src/vaa/core.rs b/sdk/rust/core/src/vaa/core.rs new file mode 100644 index 000000000..cdb37e5e6 --- /dev/null +++ b/sdk/rust/core/src/vaa/core.rs @@ -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, + }, + )) + } +} diff --git a/sdk/rust/core/src/vaa/nft.rs b/sdk/rust/core/src/vaa/nft.rs new file mode 100644 index 000000000..4402171c7 --- /dev/null +++ b/sdk/rust/core/src/vaa/nft.rs @@ -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>(input: T) -> Result { + 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 })) + } +} diff --git a/sdk/rust/core/src/vaa/token.rs b/sdk/rust/core/src/vaa/token.rs new file mode 100644 index 000000000..7e2bf3cf1 --- /dev/null +++ b/sdk/rust/core/src/vaa/token.rs @@ -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>(input: T) -> Result { + 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>(input: T) -> Result { + 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 })) + } +}