Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions solana/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions solana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ members = [
"solitaire/program",
"solitaire/rocksalt",
]
exclude = [
"modules/token_bridge/token_metadata_parser",
]

[patch.crates-io]
memmap2 = { path = "bridge/memmap2-rs" }
spl-token = { git = "https://github.com/wormholelabs-xyz/spl-token.git", rev = "7ae1b55" }
4 changes: 2 additions & 2 deletions solana/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ nft_bridge_AUTHORITY_mainnet=3cVZHphy4QUYnU1hYFyvHF9joeZJ6ZTxpWx1nzavaUa8

# Testnet buffer authority is the deployer public key
bridge_ADDRESS_testnet=3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5
bridge_AUTHORITY_testnet=9r6q2iEg4MBevjC8reaLmQUDxueF3vabUoqDkZ2LoAYe
bridge_AUTHORITY_testnet=J8am6SkUHRTtLPJpnfUd6Uy38U7Yh17fa7ZtiqaLoJcV
token_bridge_ADDRESS_testnet=DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe
token_bridge_AUTHORITY_testnet=9r6q2iEg4MBevjC8reaLmQUDxueF3vabUoqDkZ2LoAYe
token_bridge_AUTHORITY_testnet=FQAHqBcVHiiiLP8qXKPDQGr3mEXLv7RSdvfHJ3ZLugBV
nft_bridge_ADDRESS_testnet=2rHhojZ7hpu1zA91nvZmT8TqWWvMcKmmNBCr2mKTtMq4
nft_bridge_AUTHORITY_testnet=9r6q2iEg4MBevjC8reaLmQUDxueF3vabUoqDkZ2LoAYe

Expand Down
8 changes: 7 additions & 1 deletion solana/bridge/program/src/accounts/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ use serde::{
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::Seeded,
processors::seeded::{
Seeded,
SingleOwned,
},
AccountOwner,
AccountState::*,
CreationLamports::Exempt,
Expand Down Expand Up @@ -100,6 +103,9 @@ impl Owned for ClaimData {
}
}

impl SingleOwned for ClaimData {
}

pub struct ClaimDerivationData {
pub emitter_address: [u8; 32],
pub emitter_chain: u16,
Expand Down
8 changes: 7 additions & 1 deletion solana/bridge/program/src/accounts/guardian_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use serde::{
Serialize,
};
use solitaire::{
processors::seeded::Seeded,
processors::seeded::{
Seeded,
SingleOwned,
},
AccountOwner,
AccountState,
Data,
Expand Down Expand Up @@ -63,3 +66,6 @@ impl Owned for GuardianSetData {
AccountOwner::This
}
}

impl SingleOwned for GuardianSetData {
}
8 changes: 7 additions & 1 deletion solana/bridge/program/src/accounts/posted_vaa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use borsh::{
BorshSerialize,
};
use solitaire::{
processors::seeded::Seeded,
processors::seeded::{
Seeded,
SingleOwned,
},
AccountOwner,
AccountState,
Data,
Expand Down Expand Up @@ -94,6 +97,9 @@ impl Owned for PostedVAAData {
}
}

impl SingleOwned for PostedVAAData {
}

#[cfg(feature = "cpi")]
impl Owned for PostedVAAData {
fn owner(&self) -> AccountOwner {
Expand Down
8 changes: 7 additions & 1 deletion solana/bridge/program/src/accounts/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use borsh::{
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::Seeded,
processors::seeded::{
Seeded,
SingleOwned,
},
AccountOwner,
AccountState,
Data,
Expand Down Expand Up @@ -36,3 +39,6 @@ impl Owned for SequenceTracker {
AccountOwner::This
}
}

impl SingleOwned for SequenceTracker {
}
4 changes: 4 additions & 0 deletions solana/migration/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use solitaire::{
processors::seeded::{
AccountOwner,
Owned,
SingleOwned,
},
};
use spl_token::state::{
Expand All @@ -31,5 +32,8 @@ impl Owned for PoolData {
}
}

impl SingleOwned for PoolData {
}

pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));
7 changes: 7 additions & 0 deletions solana/modules/nft_bridge/program/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use solitaire::{
processors::seeded::{
AccountOwner,
Owned,
SingleOwned,
},
};
use spl_token::state::{
Expand Down Expand Up @@ -45,6 +46,9 @@ impl Owned for EndpointRegistration {
}
}

impl SingleOwned for EndpointRegistration {
}

#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub struct WrappedMeta {
pub chain: ChainID,
Expand All @@ -58,5 +62,8 @@ impl Owned for WrappedMeta {
}
}

impl SingleOwned for WrappedMeta {
}

pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));
1 change: 1 addition & 0 deletions solana/modules/token_bridge/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ instructions = []

[dependencies]
wormhole-bridge-solana = { path = "../../../bridge/program", features = ["no-entrypoint", "cpi"] }
token-metadata-parser = { path = "../token_metadata_parser" }
borsh = "=0.9.3"
bstr = "0.2.16"
byteorder = "1.4.3"
Expand Down
130 changes: 117 additions & 13 deletions solana/modules/token_bridge/program/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ impl<'b, const STATE: AccountState> Seeded<&EndpointDerivationData> for Endpoint
}
}

/// Metadata account for the token. This used to be exclusively the Metaplex
/// metadata account, but with token2022's metadata pointer extension, this may be any account.
/// `deserialize_and_verify_metadata` verifies that this account is what the token specifies (or falls back to Metaplex).
pub type SplTokenMeta<'b> = Info<'b>;

pub struct SplTokenMetaDerivationData {
Expand All @@ -106,39 +109,140 @@ impl<'b> Seeded<&SplTokenMetaDerivationData> for SplTokenMeta<'b> {
}
}

//New data length of spl token metadata account
pub const NEW_MAX_METADATA_LEN: usize = 607;

/// Converts Token-2022 metadata to Metaplex metadata format for compatibility
fn convert_token2022_to_metaplex_metadata(
token_metadata: &token_metadata_parser::TokenMetadata,
) -> spl_token_metadata::state::Metadata {
use spl_token_metadata::state::{
Data,
Key,
Metadata,
};

let data = Data {
name: token_metadata.name.clone(),
symbol: token_metadata.symbol.clone(),
uri: token_metadata.uri.clone(),
seller_fee_basis_points: 0, // Token-2022 doesn't have this concept
creators: None, // Token-2022 doesn't have creators
};

Metadata {
key: Key::MetadataV1,
update_authority: token_metadata
.update_authority
.map(|pubkey| Pubkey::new(&pubkey.0))
.unwrap_or_default(),
mint: Pubkey::new(&token_metadata.mint.0),
data,
primary_sale_happened: false, // Default for Token-2022
is_mutable: token_metadata.update_authority.is_some(),
edition_nonce: None,
token_standard: None,
collection: None,
uses: None,
collection_details: None,
programmable_config: None,
}
}

/// This method removes code duplication when checking token metadata. When metadata is read for
/// attestation and transfers, Token Bridge does not invoke Metaplex's Token Metadata program, so
/// it must validate the account the same way Token Metadata program does to ensure the correct
/// account is passed into Token Bridge's instruction context.
pub fn deserialize_and_verify_metadata(
info: &Info,
mint: &Info,
metadata: &Info,
derivation_data: SplTokenMetaDerivationData,
) -> Result<spl_token_metadata::state::Metadata> {
// Verify pda.
info.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
) -> Result<Option<spl_token_metadata::state::Metadata>> {
let mint_metadata = token_metadata_parser::parse_token2022_metadata(
token_metadata_parser::Pubkey::new(mint.key.to_bytes()),
&mint.data.borrow(),
)
.map_err(|_| TokenBridgeError::InvalidMetadata)?;

// we constrain the `metadata` account in every case.
// 1. if mint is token-2022 with embedded metadata, we return that metadata (in this case, `metadata` == `mint`. `token_metadata_parser` ensures this)
// 2. if mint is token-2022 with external metadata pointer, we verify `metadata` matches the pointer
// a. if `metadata` is owned by spl-token-metadata, we verify the pda and deserialise it as standard Metaplex metadata
// b. if `metadata` is not owned by spl-token-metadata, we don't verify that it's a pda (we know it matches the pointer already)
// 3. if mint doesn't include a metadata pointer, we ensure `metadata` is the metaplex pda.
// this is the legacy case, but it applies to token2022 tokens as well (that have no metadata pointer extension)
//
// Note that in every case other than 1 (which is a well-defined spec via
// the token metadata extension), we parse the `metadata` account following
// the standard metaplex format.
//
// In case of 2b, this is a best-effort guess, because the metadata pointer
// extension makes no guarantees about the shape of the metadata account. However, a common practice is to just follow the metaplex format.
// What this means is that if the metadata account is not owned by the metaplex program, and is not in the metaplex format, the deserialisation will fail.
// We just don't support these tokens.

match mint_metadata {
// 1.
token_metadata_parser::MintMetadata::Embedded(token_metadata) => {
// token-2022 mint with embedded metadata
Ok(Some(convert_token2022_to_metaplex_metadata(
&token_metadata,
)))
}
// 2.
token_metadata_parser::MintMetadata::External(pointer) => {
if pointer.metadata_address
!= token_metadata_parser::Pubkey::new(metadata.key.to_bytes())
{
return Err(TokenBridgeError::WrongMetadataAccount.into());
}

// There must be account data for token's metadata.
if info.data_is_empty() {
return Err(TokenBridgeError::NonexistentTokenMetadataAccount.into());
// 2a.
if *metadata.owner == spl_token_metadata::id() {
// Standard Metaplex metadata verification and parsing
// Verify pda.
metadata.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
}
deserialize_metaplex_formatted_metadata_account(metadata)
}
// 3.
token_metadata_parser::MintMetadata::None => {
// Account must belong to Metaplex Token Metadata program.
if *metadata.owner != Pubkey::default() && *metadata.owner != spl_token_metadata::id() {
return Err(TokenBridgeError::WrongAccountOwner.into());
}
// Standard Metaplex metadata verification and parsing
// Verify pda.
metadata.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
deserialize_metaplex_formatted_metadata_account(metadata)
}
}
}

// Account must belong to Metaplex Token Metadata program.
if *info.owner != spl_token_metadata::id() {
return Err(TokenBridgeError::WrongAccountOwner.into());
/// Deserialises a Metaplex formatted metadata account. Here we assume that the
/// metadata account has been checked to be the correct account for the mint.
/// As such, if it's empty, it just means there is no metadata.
fn deserialize_metaplex_formatted_metadata_account(
metadata: &Info,
) -> Result<Option<spl_token_metadata::state::Metadata>> {
if metadata.data_is_empty() {
return Ok(None);
}

// Account must be the expected Metadata length.
if info.data_len() != spl_token_metadata::state::MAX_METADATA_LEN {
if metadata.data_len() != spl_token_metadata::state::MAX_METADATA_LEN
&& metadata.data_len() != NEW_MAX_METADATA_LEN
{
return Err(TokenBridgeError::InvalidMetadata.into());
}

let mut data: &[u8] = &info.data.borrow_mut();
let mut data: &[u8] = &metadata.data.borrow_mut();

// Unfortunately we cannot use `map_err` easily, so we will match certain deserialization conditions.
match spl_token_metadata::utils::meta_deser_unchecked(&mut data) {
Ok(deserialized) => {
if deserialized.key == MetadataV1 {
Ok(deserialized)
Ok(Some(deserialized))
} else {
Err(TokenBridgeError::NotMetadataV1Account.into())
}
Expand Down
5 changes: 3 additions & 2 deletions solana/modules/token_bridge/program/src/api/attest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@ pub fn attest_token(
};

// Assign metadata if an SPL Metadata account exists for the SPL token in question.
if !accs.spl_metadata.data_is_empty() {
let metadata = deserialize_and_verify_metadata(&accs.spl_metadata, (&*accs).into())?;
if let Some(metadata) =
deserialize_and_verify_metadata(accs.mint.info(), &accs.spl_metadata, (&*accs).into())?
{
payload.name = metadata.data.name.clone();
payload.symbol = metadata.data.symbol;
}
Expand Down
Loading
Loading