418 lines
13 KiB
Rust
418 lines
13 KiB
Rust
//! Bridge transition types
|
|
|
|
use std::mem::size_of;
|
|
|
|
use primitive_types::U256;
|
|
use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
|
use zerocopy::AsBytes;
|
|
|
|
use crate::error::Error;
|
|
use crate::instruction::{ForeignAddress, VAAData};
|
|
use crate::syscalls::RawKey;
|
|
use crate::vaa::BodyTransfer;
|
|
|
|
/// fee rate as a ratio
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct Fee {
|
|
/// denominator of the fee ratio
|
|
pub denominator: u64,
|
|
/// numerator of the fee ratio
|
|
pub numerator: u64,
|
|
}
|
|
|
|
/// guardian set
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct GuardianSet {
|
|
/// index of the set
|
|
pub index: u32,
|
|
/// public key of the threshold schnorr set
|
|
pub pubkey: RawKey,
|
|
/// creation time
|
|
pub creation_time: u32,
|
|
/// expiration time when VAAs issued by this set are no longer valid
|
|
pub expiration_time: u32,
|
|
|
|
/// Is `true` if this structure has been initialized.
|
|
pub is_initialized: bool,
|
|
}
|
|
|
|
impl IsInitialized for GuardianSet {
|
|
fn is_initialized(&self) -> bool {
|
|
self.is_initialized
|
|
}
|
|
}
|
|
|
|
/// proposal to transfer tokens to a foreign chain
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct TransferOutProposal {
|
|
/// amount to transfer
|
|
pub amount: U256,
|
|
/// chain id to transfer to
|
|
pub to_chain_id: u8,
|
|
/// address the transfer was initiated from
|
|
pub source_address: ForeignAddress,
|
|
/// address on the foreign chain to transfer to
|
|
pub foreign_address: ForeignAddress,
|
|
/// asset that is being transferred
|
|
pub asset: AssetMeta,
|
|
/// nonce of the transfer
|
|
pub nonce: u32,
|
|
/// vaa to unlock the tokens on the foreign chain
|
|
pub vaa: VAAData,
|
|
/// time the vaa was submitted
|
|
pub vaa_time: u32,
|
|
|
|
/// Is `true` if this structure has been initialized.
|
|
pub is_initialized: bool,
|
|
}
|
|
|
|
impl IsInitialized for TransferOutProposal {
|
|
fn is_initialized(&self) -> bool {
|
|
self.is_initialized
|
|
}
|
|
}
|
|
|
|
impl TransferOutProposal {
|
|
pub fn matches_vaa(&self, b: &BodyTransfer) -> bool {
|
|
return b.amount == self.amount
|
|
&& b.target_address == self.foreign_address
|
|
&& b.target_chain == self.to_chain_id
|
|
&& b.asset == self.asset;
|
|
}
|
|
}
|
|
|
|
/// record of a claimed VAA
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
pub struct ClaimedVAA {
|
|
/// hash of the vaa
|
|
pub hash: [u8; 32],
|
|
/// time the vaa was submitted
|
|
pub vaa_time: u32,
|
|
|
|
/// Is `true` if this structure has been initialized.
|
|
pub is_initialized: bool,
|
|
}
|
|
|
|
impl IsInitialized for ClaimedVAA {
|
|
fn is_initialized(&self) -> bool {
|
|
self.is_initialized
|
|
}
|
|
}
|
|
|
|
/// metadata tracking for wrapped assets
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
pub struct WrappedAssetMeta {
|
|
/// chain id of the native chain of this asset
|
|
pub chain: u8,
|
|
/// address of the asset on the native chain
|
|
pub address: ForeignAddress,
|
|
|
|
/// Is `true` if this structure has been initialized.
|
|
pub is_initialized: bool,
|
|
}
|
|
|
|
impl IsInitialized for WrappedAssetMeta {
|
|
fn is_initialized(&self) -> bool {
|
|
self.is_initialized
|
|
}
|
|
}
|
|
|
|
/// Metadata about an asset
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
pub struct AssetMeta {
|
|
/// Address of the token
|
|
pub address: ForeignAddress,
|
|
|
|
/// Chain of the token
|
|
pub chain: u8,
|
|
}
|
|
|
|
/// Config for a bridge.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
pub struct BridgeConfig {
|
|
/// Period for how long a VAA is valid. This is also the period after a valid VAA has been
|
|
/// published to a `TransferOutProposal` or `ClaimedVAA` after which the account can be evicted.
|
|
/// This exists to guarantee data availability and prevent replays.
|
|
pub vaa_expiration_time: u32,
|
|
|
|
/// Token program that is used for this bridge
|
|
pub token_program: Pubkey,
|
|
}
|
|
|
|
/// Bridge state.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct Bridge {
|
|
/// the currently active guardian set
|
|
pub guardian_set_index: u32,
|
|
|
|
/// read-only config parameters for a bridge instance.
|
|
pub config: BridgeConfig,
|
|
|
|
/// Is `true` if this structure has been initialized.
|
|
pub is_initialized: bool,
|
|
}
|
|
|
|
impl IsInitialized for Bridge {
|
|
fn is_initialized(&self) -> bool {
|
|
self.is_initialized
|
|
}
|
|
}
|
|
|
|
/// Implementation of serialization functions
|
|
impl Bridge {
|
|
/// Deserializes a spl_token `Account`.
|
|
pub fn token_account_deserialize(
|
|
info: &AccountInfo,
|
|
) -> Result<spl_token::state::Account, Error> {
|
|
Ok(*spl_token::state::unpack(&mut info.data.borrow_mut())
|
|
.map_err(|_| Error::ExpectedAccount)?)
|
|
}
|
|
|
|
/// Deserializes a spl_token `Mint`.
|
|
pub fn mint_deserialize(info: &AccountInfo) -> Result<spl_token::state::Mint, Error> {
|
|
Ok(*spl_token::state::unpack(&mut info.data.borrow_mut())
|
|
.map_err(|_| Error::ExpectedToken)?)
|
|
}
|
|
|
|
/// Deserializes a `Bridge`.
|
|
pub fn bridge_deserialize(info: &AccountInfo) -> Result<Bridge, Error> {
|
|
Ok(*Bridge::unpack(&mut info.data.borrow_mut()).map_err(|_| Error::ExpectedBridge)?)
|
|
}
|
|
|
|
/// Deserializes a `GuardianSet`.
|
|
pub fn guardian_set_deserialize(info: &AccountInfo) -> Result<GuardianSet, Error> {
|
|
Ok(*Bridge::unpack(&mut info.data.borrow_mut()).map_err(|_| Error::ExpectedGuardianSet)?)
|
|
}
|
|
|
|
/// Deserializes a `WrappedAssetMeta`.
|
|
pub fn wrapped_meta_deserialize(info: &AccountInfo) -> Result<WrappedAssetMeta, Error> {
|
|
Ok(*Bridge::unpack(&mut info.data.borrow_mut())
|
|
.map_err(|_| Error::ExpectedWrappedAssetMeta)?)
|
|
}
|
|
|
|
/// Deserializes a `TransferOutProposal`.
|
|
pub fn transfer_out_proposal_deserialize(
|
|
info: &AccountInfo,
|
|
) -> Result<TransferOutProposal, Error> {
|
|
Ok(*Bridge::unpack(&mut info.data.borrow_mut())
|
|
.map_err(|_| Error::ExpectedTransferOutProposal)?)
|
|
}
|
|
|
|
/// Unpacks a state from a bytes buffer while assuring that the state is initialized.
|
|
pub fn unpack<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
|
|
let mut_ref: &mut T = Self::unpack_unchecked(input)?;
|
|
if !mut_ref.is_initialized() {
|
|
return Err(Error::UninitializedState.into());
|
|
}
|
|
Ok(mut_ref)
|
|
}
|
|
|
|
/// Unpacks a state from a bytes buffer without checking that the state is initialized.
|
|
pub fn unpack_unchecked<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
|
|
if input.len() != size_of::<T>() {
|
|
return Err(ProgramError::InvalidAccountData);
|
|
}
|
|
#[allow(clippy::cast_ptr_alignment)]
|
|
Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
|
|
}
|
|
|
|
/// Unpacks a state from a bytes buffer while assuring that the state is initialized.
|
|
pub fn unpack_immutable<T: IsInitialized>(input: &[u8]) -> Result<&T, ProgramError> {
|
|
let mut_ref: &T = Self::unpack_unchecked_immutable(input)?;
|
|
if !mut_ref.is_initialized() {
|
|
return Err(Error::UninitializedState.into());
|
|
}
|
|
Ok(mut_ref)
|
|
}
|
|
|
|
/// Unpacks a state from a bytes buffer without checking that the state is initialized.
|
|
pub fn unpack_unchecked_immutable<T: IsInitialized>(input: &[u8]) -> Result<&T, ProgramError> {
|
|
if input.len() != size_of::<T>() {
|
|
return Err(ProgramError::InvalidAccountData);
|
|
}
|
|
#[allow(clippy::cast_ptr_alignment)]
|
|
Ok(unsafe { &*(&input[0] as *const u8 as *const T) })
|
|
}
|
|
}
|
|
|
|
/// Implementation of derivations
|
|
impl Bridge {
|
|
/// Calculates derived seeds for a guardian set
|
|
pub fn derive_guardian_set_seeds(bridge_key: &Pubkey, guardian_set_index: u32) -> Vec<Vec<u8>> {
|
|
vec![
|
|
"guardian".as_bytes().to_vec(),
|
|
bridge_key.to_bytes().to_vec(),
|
|
guardian_set_index.as_bytes().to_vec(),
|
|
]
|
|
}
|
|
|
|
/// Calculates derived seeds for a wrapped asset
|
|
pub fn derive_wrapped_asset_seeds(
|
|
bridge_key: &Pubkey,
|
|
asset_chain: u8,
|
|
asset: ForeignAddress,
|
|
) -> Vec<Vec<u8>> {
|
|
vec![
|
|
"wrapped".as_bytes().to_vec(),
|
|
bridge_key.to_bytes().to_vec(),
|
|
asset_chain.as_bytes().to_vec(),
|
|
asset.as_bytes().to_vec(),
|
|
]
|
|
}
|
|
|
|
/// Calculates derived seeds for a transfer out
|
|
pub fn derive_transfer_id_seeds(
|
|
bridge_key: &Pubkey,
|
|
asset_chain: u8,
|
|
asset: ForeignAddress,
|
|
target_chain: u8,
|
|
target_address: ForeignAddress,
|
|
user: ForeignAddress,
|
|
slot: u32,
|
|
) -> Vec<Vec<u8>> {
|
|
vec![
|
|
"transfer".as_bytes().to_vec(),
|
|
bridge_key.to_bytes().to_vec(),
|
|
asset_chain.as_bytes().to_vec(),
|
|
asset.as_bytes().to_vec(),
|
|
target_chain.as_bytes().to_vec(),
|
|
target_address.as_bytes().to_vec(),
|
|
user.as_bytes().to_vec(),
|
|
slot.as_bytes().to_vec(),
|
|
]
|
|
}
|
|
|
|
/// Calculates derived seeds for a bridge
|
|
pub fn derive_bridge_seeds() -> Vec<Vec<u8>> {
|
|
vec!["bridge".as_bytes().to_vec()]
|
|
}
|
|
|
|
/// Calculates derived seeds for a custody account
|
|
pub fn derive_custody_seeds<'a>(bridge: &Pubkey, mint: &Pubkey) -> Vec<Vec<u8>> {
|
|
vec![
|
|
"custody".as_bytes().to_vec(),
|
|
bridge.to_bytes().to_vec(),
|
|
mint.to_bytes().to_vec(),
|
|
]
|
|
}
|
|
|
|
/// Calculates derived seeds for a claim
|
|
pub fn derive_claim_seeds<'a>(bridge: &Pubkey, hash: &[u8; 32]) -> Vec<Vec<u8>> {
|
|
vec![
|
|
"claim".as_bytes().to_vec(),
|
|
bridge.to_bytes().to_vec(),
|
|
hash.as_bytes().to_vec(),
|
|
]
|
|
}
|
|
|
|
/// Calculates derived seeds for a wrapped asset meta entry
|
|
pub fn derive_wrapped_meta_seeds<'a>(bridge: &Pubkey, mint: &Pubkey) -> Vec<Vec<u8>> {
|
|
vec![
|
|
"claim".as_bytes().to_vec(),
|
|
bridge.to_bytes().to_vec(),
|
|
mint.to_bytes().to_vec(),
|
|
]
|
|
}
|
|
|
|
/// Calculates a derived address for this program
|
|
pub fn derive_bridge_id(program_id: &Pubkey) -> Result<Pubkey, Error> {
|
|
Self::derive_key(program_id, &Self::derive_bridge_seeds())
|
|
}
|
|
|
|
/// Calculates a derived address for a custody account
|
|
pub fn derive_custody_id(
|
|
program_id: &Pubkey,
|
|
bridge: &Pubkey,
|
|
mint: &Pubkey,
|
|
) -> Result<Pubkey, Error> {
|
|
Self::derive_key(program_id, &Self::derive_custody_seeds(bridge, mint))
|
|
}
|
|
|
|
/// Calculates a derived address for a claim account
|
|
pub fn derive_claim_id(
|
|
program_id: &Pubkey,
|
|
bridge: &Pubkey,
|
|
hash: &[u8; 32],
|
|
) -> Result<Pubkey, Error> {
|
|
Self::derive_key(program_id, &Self::derive_claim_seeds(bridge, hash))
|
|
}
|
|
|
|
/// Calculates a derived address for a wrapped asset meta entry
|
|
pub fn derive_wrapped_meta_id(
|
|
program_id: &Pubkey,
|
|
bridge: &Pubkey,
|
|
mint: &Pubkey,
|
|
) -> Result<Pubkey, Error> {
|
|
Self::derive_key(program_id, &Self::derive_wrapped_meta_seeds(bridge, mint))
|
|
}
|
|
|
|
/// Calculates a derived address for this program
|
|
pub fn derive_guardian_set_id(
|
|
program_id: &Pubkey,
|
|
bridge_key: &Pubkey,
|
|
guardian_set_index: u32,
|
|
) -> Result<Pubkey, Error> {
|
|
Self::derive_key(
|
|
program_id,
|
|
&Self::derive_guardian_set_seeds(bridge_key, guardian_set_index),
|
|
)
|
|
}
|
|
|
|
/// Calculates a derived seeds for a wrapped asset
|
|
pub fn derive_wrapped_asset_id(
|
|
program_id: &Pubkey,
|
|
bridge_key: &Pubkey,
|
|
asset_chain: u8,
|
|
asset: ForeignAddress,
|
|
) -> Result<Pubkey, Error> {
|
|
Self::derive_key(
|
|
program_id,
|
|
&Self::derive_wrapped_asset_seeds(bridge_key, asset_chain, asset),
|
|
)
|
|
}
|
|
|
|
/// Calculates a derived address for a transfer out
|
|
pub fn derive_transfer_id(
|
|
program_id: &Pubkey,
|
|
bridge_key: &Pubkey,
|
|
asset_chain: u8,
|
|
asset: ForeignAddress,
|
|
target_chain: u8,
|
|
target_address: ForeignAddress,
|
|
user: ForeignAddress,
|
|
slot: u32,
|
|
) -> Result<Pubkey, Error> {
|
|
Self::derive_key(
|
|
program_id,
|
|
&Self::derive_transfer_id_seeds(
|
|
bridge_key,
|
|
asset_chain,
|
|
asset,
|
|
target_chain,
|
|
target_address,
|
|
user,
|
|
slot,
|
|
),
|
|
)
|
|
}
|
|
|
|
pub fn derive_key(program_id: &Pubkey, seeds: &Vec<Vec<u8>>) -> Result<Pubkey, Error> {
|
|
let s: Vec<_> = seeds.iter().map(|item| item.as_slice()).collect();
|
|
Ok(Pubkey::find_program_address(s.as_slice(), program_id).0)
|
|
}
|
|
}
|
|
|
|
/// Check is a token state is initialized
|
|
pub trait IsInitialized {
|
|
/// Is initialized
|
|
fn is_initialized(&self) -> bool;
|
|
}
|