sdk: add core types for wormhole projects
Change-Id: Iabaebb7b4319d5b759044896b2b396d39bda33d7
This commit is contained in:
parent
d21f08d2cb
commit
97566d878a
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"core",
|
||||
]
|
|
@ -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 = "*"
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
|
@ -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 }))
|
||||
}
|
||||
}
|
|
@ -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 }))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue