Browse Source

Add Metadata to token bridge attestations

Change-Id: Ic1a10978c25fbd916a16bd08eab0b6937c67cd59
Reisen 4 years ago
parent
commit
05aece1f7c

+ 0 - 2
solana/bridge/program/src/api/post_message.rs

@@ -123,8 +123,6 @@ pub fn post_message(
             .create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
             .create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
     }
     }
 
 
-    msg!("Sequence: {}", accs.sequence.sequence);
-
     // Initialize transfer
     // Initialize transfer
     trace!("Setting Message Details");
     trace!("Setting Message Details");
     accs.message.submission_time = accs.clock.unix_timestamp as u32;
     accs.message.submission_time = accs.clock.unix_timestamp as u32;

+ 10 - 0
solana/modules/token_bridge/Cargo.lock

@@ -3159,6 +3159,15 @@ dependencies = [
  "thiserror",
  "thiserror",
 ]
 ]
 
 
+[[package]]
+name = "spl-token-metadata"
+version = "0.0.1"
+dependencies = [
+ "borsh",
+ "solana-program",
+ "spl-token",
+]
+
 [[package]]
 [[package]]
 name = "stable_deref_trait"
 name = "stable_deref_trait"
 version = "1.2.0"
 version = "1.2.0"
@@ -3376,6 +3385,7 @@ dependencies = [
  "solitaire",
  "solitaire",
  "solitaire-client",
  "solitaire-client",
  "spl-token",
  "spl-token",
+ "spl-token-metadata",
  "wasm-bindgen",
  "wasm-bindgen",
 ]
 ]
 
 

+ 2 - 0
solana/modules/token_bridge/program/Cargo.toml

@@ -27,6 +27,7 @@ solana-program = "*"
 spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
 spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
 primitive-types = { version = "0.9.0", default-features = false }
 primitive-types = { version = "0.9.0", default-features = false }
 solitaire-client = { path = "../../../solitaire/client", optional = true }
 solitaire-client = { path = "../../../solitaire/client", optional = true }
+spl-token-metadata = { path = "../token-metadata" }
 wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
 wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
 serde = { version = "1.0", features = ["derive"] }
 serde = { version = "1.0", features = ["derive"] }
 rand = { version = "0.7.3", optional = true }
 rand = { version = "0.7.3", optional = true }
@@ -38,3 +39,4 @@ libsecp256k1 = { version = "0.3.5", features = [] }
 solana-client = "1.7.0"
 solana-client = "1.7.0"
 solana-sdk = "=1.7.0"
 solana-sdk = "=1.7.0"
 spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
 spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
+spl-token-metadata = { path = "../token-metadata" }

+ 43 - 6
solana/modules/token_bridge/program/src/api/attest.rs

@@ -2,12 +2,18 @@ use crate::{
     accounts::{
     accounts::{
         ConfigAccount,
         ConfigAccount,
         EmitterAccount,
         EmitterAccount,
+        WrappedMetaDerivationData,
+        WrappedTokenMeta,
     },
     },
     messages::{
     messages::{
         PayloadAssetMeta,
         PayloadAssetMeta,
         PayloadTransfer,
         PayloadTransfer,
     },
     },
     types::*,
     types::*,
+    TokenBridgeError::{
+        self,
+        *,
+    },
 };
 };
 use bridge::{
 use bridge::{
     api::{
     api::{
@@ -33,7 +39,11 @@ use solana_program::{
     sysvar::clock::Clock,
     sysvar::clock::Clock,
 };
 };
 use solitaire::{
 use solitaire::{
-    processors::seeded::invoke_seeded,
+    processors::seeded::{
+        invoke_seeded,
+        Owned,
+        Seeded,
+    },
     CreationLamports::Exempt,
     CreationLamports::Exempt,
     *,
     *,
 };
 };
@@ -44,6 +54,7 @@ use spl_token::{
         Mint,
         Mint,
     },
     },
 };
 };
+use spl_token_metadata::state::Metadata;
 use std::ops::{
 use std::ops::{
     Deref,
     Deref,
     DerefMut,
     DerefMut,
@@ -57,7 +68,10 @@ pub struct AttestToken<'b> {
 
 
     /// Mint to attest
     /// Mint to attest
     pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
     pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
-    pub mint_meta: Data<'b, SplMint, { AccountState::MaybeInitialized }>,
+    pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Uninitialized }>,
+
+    /// SPL Metadata for the associated Mint
+    pub spl_metadata: Info<'b>,
 
 
     /// CPI Context
     /// CPI Context
     pub bridge: Mut<Info<'b>>,
     pub bridge: Mut<Info<'b>>,
@@ -80,6 +94,14 @@ pub struct AttestToken<'b> {
 impl<'b> InstructionContext<'b> for AttestToken<'b> {
 impl<'b> InstructionContext<'b> for AttestToken<'b> {
 }
 }
 
 
+impl<'a> From<&AttestToken<'a>> for WrappedMetaDerivationData {
+    fn from(accs: &AttestToken<'a>) -> Self {
+        WrappedMetaDerivationData {
+            mint_key: *accs.mint.info().key,
+        }
+    }
+}
+
 #[derive(BorshDeserialize, BorshSerialize, Default)]
 #[derive(BorshDeserialize, BorshSerialize, Default)]
 pub struct AttestTokenData {
 pub struct AttestTokenData {
     pub nonce: u32,
     pub nonce: u32,
@@ -93,18 +115,33 @@ pub fn attest_token(
     // Pay fee
     // Pay fee
     let transfer_ix =
     let transfer_ix =
         solana_program::system_instruction::transfer(accs.payer.key, accs.fee_collector.key, 1000);
         solana_program::system_instruction::transfer(accs.payer.key, accs.fee_collector.key, 1000);
+
     invoke(&transfer_ix, ctx.accounts)?;
     invoke(&transfer_ix, ctx.accounts)?;
 
 
-    let payload = PayloadAssetMeta {
+    // Enfoce wrapped meta to be uninitialized.
+    let derivation_data: WrappedMetaDerivationData = (&*accs).into();
+    accs.wrapped_meta
+        .verify_derivation(ctx.program_id, &derivation_data)?;
+
+    // Create Asset Metadata
+    let mut payload = PayloadAssetMeta {
         token_address: accs.mint.info().key.to_bytes(),
         token_address: accs.mint.info().key.to_bytes(),
         token_chain: 1,
         token_chain: 1,
         decimals: accs.mint.decimals,
         decimals: accs.mint.decimals,
-        symbol: "".to_string(), // TODO metadata
+        symbol: "".to_string(),
         name: "".to_string(),
         name: "".to_string(),
     };
     };
 
 
-    if accs.mint_meta.is_initialized() {
-        // Populate fields
+    // Assign metadata if an SPL Metadata account exists for the SPL token in question.
+    if !accs.spl_metadata.data_is_empty() {
+        if *accs.spl_metadata.owner != spl_token_metadata::id() {
+            return Err(WrongAccountOwner.into());
+        }
+
+        let metadata: Metadata =
+            Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?;
+        payload.name = metadata.data.name.clone();
+        payload.symbol = metadata.data.symbol.clone();
     }
     }
 
 
     let params = (
     let params = (

+ 7 - 3
solana/modules/token_bridge/program/src/instructions.rs

@@ -464,6 +464,9 @@ pub fn attest(
     mint: Pubkey,
     mint: Pubkey,
     decimals: u8,
     decimals: u8,
     mint_meta: Pubkey,
     mint_meta: Pubkey,
+    spl_metadata: Pubkey,
+    symbol: String,
+    name: String,
     nonce: u32,
     nonce: u32,
 ) -> solitaire::Result<Instruction> {
 ) -> solitaire::Result<Instruction> {
     let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
     let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
@@ -475,16 +478,16 @@ pub fn attest(
         token_address: mint.to_bytes(),
         token_address: mint.to_bytes(),
         token_chain: 1,
         token_chain: 1,
         decimals,
         decimals,
-        symbol: "".to_string(), // TODO metadata
-        name: "".to_string(),
+        symbol,
+        name,
     };
     };
     let message_key = Message::<'_, { AccountState::Uninitialized }>::key(
     let message_key = Message::<'_, { AccountState::Uninitialized }>::key(
         &MessageDerivationData {
         &MessageDerivationData {
             emitter_key: emitter_key.to_bytes(),
             emitter_key: emitter_key.to_bytes(),
             emitter_chain: 1,
             emitter_chain: 1,
             nonce,
             nonce,
-            sequence: None,
             payload: payload.try_to_vec().unwrap(),
             payload: payload.try_to_vec().unwrap(),
+            sequence: None,
         },
         },
         &bridge_id,
         &bridge_id,
     );
     );
@@ -503,6 +506,7 @@ pub fn attest(
             AccountMeta::new(config_key, false),
             AccountMeta::new(config_key, false),
             AccountMeta::new_readonly(mint, false),
             AccountMeta::new_readonly(mint, false),
             AccountMeta::new_readonly(mint_meta, false),
             AccountMeta::new_readonly(mint_meta, false),
+            AccountMeta::new_readonly(spl_metadata, false),
             // Bridge accounts
             // Bridge accounts
             AccountMeta::new(bridge_config, false),
             AccountMeta::new(bridge_config, false),
             AccountMeta::new(message_key, false),
             AccountMeta::new(message_key, false),

+ 10 - 15
solana/modules/token_bridge/program/src/lib.rs

@@ -53,26 +53,21 @@ use solitaire::*;
 use std::error::Error;
 use std::error::Error;
 
 
 pub enum TokenBridgeError {
 pub enum TokenBridgeError {
-    InvalidPayload,
-    Unknown(String),
-    InvalidMint,
-    WrongAccountOwner,
-    InvalidUTF8String,
     AlreadyExecuted,
     AlreadyExecuted,
     InvalidChain,
     InvalidChain,
-    TokenNotNative,
     InvalidGovernanceKey,
     InvalidGovernanceKey,
+    InvalidMetadata,
+    InvalidMint,
+    InvalidPayload,
+    InvalidUTF8String,
+    TokenNotNative,
+    UninitializedMint,
+    WrongAccountOwner,
 }
 }
 
 
-impl<T: Error> From<T> for TokenBridgeError {
-    fn from(t: T) -> Self {
-        return TokenBridgeError::Unknown(t.to_string());
-    }
-}
-
-impl Into<SolitaireError> for TokenBridgeError {
-    fn into(self) -> SolitaireError {
-        SolitaireError::Custom(0)
+impl From<TokenBridgeError> for SolitaireError {
+    fn from(t: TokenBridgeError) -> SolitaireError {
+        SolitaireError::Custom(t as u64)
     }
     }
 }
 }
 
 

+ 2 - 0
solana/modules/token_bridge/program/src/types.rs

@@ -18,6 +18,8 @@ use spl_token::state::{
     Account,
     Account,
     Mint,
     Mint,
 };
 };
+use spl_token_metadata::state::Metadata;
+use std::str::FromStr;
 
 
 pub type Address = [u8; 32];
 pub type Address = [u8; 32];
 pub type ChainID = u16;
 pub type ChainID = u16;

+ 45 - 4
solana/modules/token_bridge/program/tests/common.rs

@@ -168,11 +168,11 @@ mod helpers {
     }
     }
 
 
     /// Fetch account data, the loop is there to re-attempt until data is available.
     /// Fetch account data, the loop is there to re-attempt until data is available.
-    pub fn get_account_data<T: BorshDeserialize>(client: &RpcClient, account: &Pubkey) -> T {
+    pub fn get_account_data<T: BorshDeserialize>(client: &RpcClient, account: &Pubkey) -> Option<T> {
         let account = client
         let account = client
             .get_account_with_commitment(account, CommitmentConfig::processed())
             .get_account_with_commitment(account, CommitmentConfig::processed())
             .unwrap();
             .unwrap();
-        T::try_from_slice(&account.value.unwrap().data).unwrap()
+        T::try_from_slice(&account.value.unwrap().data).ok()
     }
     }
 
 
     pub fn initialize_bridge(
     pub fn initialize_bridge(
@@ -233,7 +233,10 @@ mod helpers {
         bridge: &Pubkey,
         bridge: &Pubkey,
         payer: &Keypair,
         payer: &Keypair,
         mint: Pubkey,
         mint: Pubkey,
-        mint_meta: Pubkey,
+        wrapped_meta: Pubkey,
+        spl_metadata: Pubkey,
+        symbol: String,
+        name: String,
         nonce: u32,
         nonce: u32,
     ) -> Result<Signature, ClientError> {
     ) -> Result<Signature, ClientError> {
         let mint_data = Mint::unpack(
         let mint_data = Mint::unpack(
@@ -255,7 +258,10 @@ mod helpers {
                 payer.pubkey(),
                 payer.pubkey(),
                 mint,
                 mint,
                 mint_data.decimals,
                 mint_data.decimals,
-                mint_meta,
+                wrapped_meta,
+                spl_metadata,
+                symbol,
+                name,
                 nonce,
                 nonce,
             )
             )
             .unwrap()],
             .unwrap()],
@@ -496,6 +502,41 @@ mod helpers {
         )
         )
     }
     }
 
 
+    pub fn create_spl_metadata(
+        client: &RpcClient,
+        payer: &Keypair,
+        metadata_account: &Pubkey,
+        mint_authority: &Keypair,
+        mint: &Keypair,
+        update_authority: &Pubkey,
+        name: String,
+        symbol: String,
+    ) -> Result<Signature, ClientError> {
+        execute(
+            client,
+            payer,
+            &[payer, mint_authority],
+            &[
+                spl_token_metadata::instruction::create_metadata_accounts(
+                    spl_token_metadata::id(),
+                    *metadata_account,
+                    mint.pubkey(),
+                    mint_authority.pubkey(),
+                    payer.pubkey(),
+                    *update_authority,
+                    name,
+                    symbol,
+                    "https://token.org".to_string(),
+                    None,
+                    0,
+                    false,
+                    false,
+                )
+            ],
+            CommitmentConfig::processed(),
+        )
+    }
+
     pub fn create_token_account(
     pub fn create_token_account(
         client: &RpcClient,
         client: &RpcClient,
         payer: &Keypair,
         payer: &Keypair,

+ 82 - 5
solana/modules/token_bridge/program/tests/integration.rs

@@ -31,6 +31,7 @@ use solana_program::{
     sysvar,
     sysvar,
 };
 };
 use solana_sdk::{
 use solana_sdk::{
+    commitment_config::CommitmentConfig,
     signature::{
     signature::{
         read_keypair_file,
         read_keypair_file,
         Keypair,
         Keypair,
@@ -42,6 +43,7 @@ use solitaire::{
     processors::seeded::Seeded,
     processors::seeded::Seeded,
     AccountState,
     AccountState,
 };
 };
+use spl_token::state::Mint;
 use std::{
 use std::{
     convert::TryInto,
     convert::TryInto,
     io::{
     io::{
@@ -91,6 +93,7 @@ use std::{
 };
 };
 use token_bridge::{
 use token_bridge::{
     accounts::{
     accounts::{
+        EmitterAccount,
         WrappedDerivationData,
         WrappedDerivationData,
         WrappedMint,
         WrappedMint,
     },
     },
@@ -130,11 +133,12 @@ struct Context {
     /// Keypairs for mint information, required in multiple tests.
     /// Keypairs for mint information, required in multiple tests.
     mint_authority: Keypair,
     mint_authority: Keypair,
     mint: Keypair,
     mint: Keypair,
-    mint_meta: Keypair,
+    mint_meta: Pubkey,
 
 
     /// Keypairs for test token accounts.
     /// Keypairs for test token accounts.
     token_authority: Keypair,
     token_authority: Keypair,
     token_account: Keypair,
     token_account: Keypair,
+    metadata_account: Pubkey,
 }
 }
 
 
 /// Small helper to track and provide sequences during tests. This is in particular needed for
 /// Small helper to track and provide sequences during tests. This is in particular needed for
@@ -164,6 +168,31 @@ fn run_integration_tests() {
     common::initialize_bridge(&client, &bridge, &payer);
     common::initialize_bridge(&client, &bridge, &payer);
 
 
     // Context for test environment.
     // Context for test environment.
+    let mint = Keypair::new();
+    let mint_pubkey = mint.pubkey();
+    let metadata_pubkey = Pubkey::from_str("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").unwrap();
+
+    // SPL Token Meta
+    let metadata_seeds = &[
+        "metadata".as_bytes(),
+        metadata_pubkey.as_ref(),
+        mint_pubkey.as_ref(),
+    ];
+
+    let (metadata_key, metadata_bump_seed) = Pubkey::find_program_address(
+        metadata_seeds,
+        &Pubkey::from_str("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").unwrap(),
+    );
+
+    // Token Bridge Meta
+    use token_bridge::accounts::WrappedTokenMeta;
+    let metadata_account = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
+        &token_bridge::accounts::WrappedMetaDerivationData {
+            mint_key: mint_pubkey.clone(),
+        },
+        &token_bridge,
+    );
+
     let mut context = Context {
     let mut context = Context {
         seq: Sequencer {
         seq: Sequencer {
             sequences: HashMap::new(),
             sequences: HashMap::new(),
@@ -173,10 +202,11 @@ fn run_integration_tests() {
         payer,
         payer,
         token_bridge,
         token_bridge,
         mint_authority: Keypair::new(),
         mint_authority: Keypair::new(),
-        mint: Keypair::new(),
-        mint_meta: Keypair::new(),
+        mint,
+        mint_meta: metadata_account,
         token_account: Keypair::new(),
         token_account: Keypair::new(),
         token_authority: Keypair::new(),
         token_authority: Keypair::new(),
+        metadata_account: metadata_key,
     };
     };
 
 
     // Create a mint for use within tests.
     // Create a mint for use within tests.
@@ -215,6 +245,20 @@ fn run_integration_tests() {
     test_attest(&mut context);
     test_attest(&mut context);
     test_register_chain(&mut context);
     test_register_chain(&mut context);
     test_transfer_native_in(&mut context);
     test_transfer_native_in(&mut context);
+
+    // Create an SPL Metadata account to test attestations for wrapped tokens.
+    common::create_spl_metadata(
+        &context.client,
+        &context.payer,
+        &context.metadata_account,
+        &context.mint_authority,
+        &context.mint,
+        &context.payer.pubkey(),
+        "BTC".to_string(),
+        "Bitcoin".to_string(),
+    )
+    .unwrap();
+
     let wrapped = test_create_wrapped(&mut context);
     let wrapped = test_create_wrapped(&mut context);
     let wrapped_acc = Keypair::new();
     let wrapped_acc = Keypair::new();
     common::create_token_account(
     common::create_token_account(
@@ -244,6 +288,7 @@ fn test_attest(context: &mut Context) -> () {
         ref mint_authority,
         ref mint_authority,
         ref mint,
         ref mint,
         ref mint_meta,
         ref mint_meta,
+        ref metadata_account,
         ..
         ..
     } = context;
     } = context;
 
 
@@ -253,10 +298,42 @@ fn test_attest(context: &mut Context) -> () {
         bridge,
         bridge,
         payer,
         payer,
         mint.pubkey(),
         mint.pubkey(),
-        mint_meta.pubkey(),
+        *mint_meta,
+        *metadata_account,
+        "".to_string(),
+        "".to_string(),
         0,
         0,
     )
     )
     .unwrap();
     .unwrap();
+
+    let emitter_key = EmitterAccount::key(None, &token_bridge);
+    let mint_data = Mint::unpack(
+        &client
+            .get_account_with_commitment(&mint.pubkey(), CommitmentConfig::processed())
+            .unwrap()
+            .value
+            .unwrap()
+            .data,
+    )
+    .unwrap();
+    let payload = PayloadAssetMeta {
+        token_address: mint.pubkey().to_bytes(),
+        token_chain: 1,
+        decimals: mint_data.decimals,
+        symbol: "USD".to_string(),
+        name: "Bitcoin".to_string(),
+    };
+    let payload = payload.try_to_vec().unwrap();
+    let message_key = Message::<'_, { AccountState::Uninitialized }>::key(
+        &MessageDerivationData {
+            emitter_key: emitter_key.to_bytes(),
+            emitter_chain: 1,
+            nonce: 0,
+            sequence: None,
+            payload: payload.clone(),
+        },
+        &token_bridge,
+    );
 }
 }
 
 
 fn test_transfer_native(context: &mut Context) -> () {
 fn test_transfer_native(context: &mut Context) -> () {
@@ -533,6 +610,6 @@ fn test_initialize(context: &mut Context) {
 
 
     // Verify Token Bridge State
     // Verify Token Bridge State
     let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &token_bridge);
     let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &token_bridge);
-    let config: Config = common::get_account_data(client, &config_key);
+    let config: Config = common::get_account_data(client, &config_key).unwrap();
     assert_eq!(config.wormhole_bridge, *bridge);
     assert_eq!(config.wormhole_bridge, *bridge);
 }
 }

+ 20 - 0
solana/modules/token_bridge/token-metadata/Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "spl-token-metadata"
+version = "0.0.1"
+description = "Metaplex Metadata"
+authors = ["Metaplex Maintainers <maintainers@metaplex.com>"]
+repository = "https://github.com/metaplex-foundation/metaplex"
+license = "Apache-2.0"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+
+[features]
+no-entrypoint = []
+test-bpf = []
+
+[dependencies]
+borsh = "0.8.1"
+solana-program = "1.7.0"
+spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }

+ 8 - 0
solana/modules/token_bridge/token-metadata/README.md

@@ -0,0 +1,8 @@
+---
+title: Token Metadata Program
+---
+
+Fork of the SPL Token Metadata program from the Metaplex repository, this is
+temporary until there are versioned releases we can compile against. Currently
+the upstream version depends on a version of solana with conflicting versions
+of borsh.

+ 2 - 0
solana/modules/token_bridge/token-metadata/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 130 - 0
solana/modules/token_bridge/token-metadata/src/instruction.rs

@@ -0,0 +1,130 @@
+use {
+    crate::{
+        state::{Creator, Data, EDITION, EDITION_MARKER_BIT_SIZE, PREFIX},
+    },
+    borsh::{BorshDeserialize, BorshSerialize},
+    solana_program::{
+        instruction::{AccountMeta, Instruction},
+        pubkey::Pubkey,
+        sysvar,
+    },
+};
+
+#[repr(C)]
+#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
+/// Args for update call
+pub struct UpdateMetadataAccountArgs {
+    pub data: Option<Data>,
+    pub update_authority: Option<Pubkey>,
+    pub primary_sale_happened: Option<bool>,
+}
+
+#[repr(C)]
+#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
+/// Args for create call
+pub struct CreateMetadataAccountArgs {
+    /// Note that unique metadatas are disabled for now.
+    pub data: Data,
+    /// Whether you want your metadata to be updateable in the future.
+    pub is_mutable: bool,
+}
+
+#[repr(C)]
+#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
+pub struct CreateMasterEditionArgs {
+    /// If set, means that no more than this number of editions can ever be minted. This is immutable.
+    pub max_supply: Option<u64>,
+}
+
+#[repr(C)]
+#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
+pub struct MintNewEditionFromMasterEditionViaTokenArgs {
+    pub edition: u64,
+}
+
+/// Instructions supported by the Metadata program.
+#[derive(BorshSerialize, BorshDeserialize, Clone)]
+pub enum MetadataInstruction {
+    /// Create Metadata object.
+    ///   0. `[writable]`  Metadata key (pda of ['metadata', program id, mint id])
+    ///   1. `[]` Mint of token asset
+    ///   2. `[signer]` Mint authority
+    ///   3. `[signer]` payer
+    ///   4. `[]` update authority info
+    ///   5. `[]` System program
+    ///   6. `[]` Rent info
+    CreateMetadataAccount(CreateMetadataAccountArgs),
+
+    /// Update a Metadata
+    ///   0. `[writable]` Metadata account
+    ///   1. `[signer]` Update authority key
+    UpdateMetadataAccount(UpdateMetadataAccountArgs),
+}
+
+/// Creates an CreateMetadataAccounts instruction
+#[allow(clippy::too_many_arguments)]
+pub fn create_metadata_accounts(
+    program_id: Pubkey,
+    metadata_account: Pubkey,
+    mint: Pubkey,
+    mint_authority: Pubkey,
+    payer: Pubkey,
+    update_authority: Pubkey,
+    name: String,
+    symbol: String,
+    uri: String,
+    creators: Option<Vec<Creator>>,
+    seller_fee_basis_points: u16,
+    update_authority_is_signer: bool,
+    is_mutable: bool,
+) -> Instruction {
+    Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(metadata_account, false),
+            AccountMeta::new_readonly(mint, false),
+            AccountMeta::new_readonly(mint_authority, true),
+            AccountMeta::new_readonly(payer, true),
+            AccountMeta::new_readonly(update_authority, update_authority_is_signer),
+            AccountMeta::new_readonly(solana_program::system_program::id(), false),
+            AccountMeta::new_readonly(sysvar::rent::id(), false),
+        ],
+        data: MetadataInstruction::CreateMetadataAccount(CreateMetadataAccountArgs {
+            data: Data {
+                name,
+                symbol,
+                uri,
+                seller_fee_basis_points,
+                creators,
+            },
+            is_mutable,
+        })
+        .try_to_vec()
+        .unwrap(),
+    }
+}
+
+/// update metadata account instruction
+pub fn update_metadata_accounts(
+    program_id: Pubkey,
+    metadata_account: Pubkey,
+    update_authority: Pubkey,
+    new_update_authority: Option<Pubkey>,
+    data: Option<Data>,
+    primary_sale_happened: Option<bool>,
+) -> Instruction {
+    Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(metadata_account, false),
+            AccountMeta::new_readonly(update_authority, true),
+        ],
+        data: MetadataInstruction::UpdateMetadataAccount(UpdateMetadataAccountArgs {
+            data,
+            update_authority: new_update_authority,
+            primary_sale_happened,
+        })
+        .try_to_vec()
+        .unwrap(),
+    }
+}

+ 7 - 0
solana/modules/token_bridge/token-metadata/src/lib.rs

@@ -0,0 +1,7 @@
+#![allow(warnings)]
+
+pub mod instruction;
+pub mod state;
+pub mod utils;
+
+solana_program::declare_id!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");

+ 124 - 0
solana/modules/token_bridge/token-metadata/src/state.rs

@@ -0,0 +1,124 @@
+use crate::{
+    utils::try_from_slice_checked,
+};
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+use solana_program::{
+    account_info::AccountInfo,
+    entrypoint::ProgramResult,
+    program_error::ProgramError,
+    pubkey::Pubkey,
+};
+
+/// prefix used for PDAs to avoid certain collision attacks (https://en.wikipedia.org/wiki/Collision_attack#Chosen-prefix_collision_attack)
+pub const PREFIX: &str = "metadata";
+
+/// Used in seeds to make Edition model pda address
+pub const EDITION: &str = "edition";
+
+pub const RESERVATION: &str = "reservation";
+
+pub const MAX_NAME_LENGTH: usize = 32;
+
+pub const MAX_SYMBOL_LENGTH: usize = 10;
+
+pub const MAX_URI_LENGTH: usize = 200;
+
+pub const MAX_METADATA_LEN: usize = 1
+    + 32
+    + 32
+    + MAX_NAME_LENGTH
+    + MAX_SYMBOL_LENGTH
+    + MAX_URI_LENGTH
+    + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN
+    + 2
+    + 1
+    + 1
+    + 198;
+
+pub const MAX_EDITION_LEN: usize = 1 + 32 + 8 + 200;
+
+// Large buffer because the older master editions have two pubkeys in them,
+// need to keep two versions same size because the conversion process actually changes the same account
+// by rewriting it.
+pub const MAX_MASTER_EDITION_LEN: usize = 1 + 9 + 8 + 264;
+
+pub const MAX_CREATOR_LIMIT: usize = 5;
+
+pub const MAX_CREATOR_LEN: usize = 32 + 1 + 1;
+
+pub const MAX_RESERVATIONS: usize = 200;
+
+// can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec
+pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100;
+
+// can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec
+pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 8 + 84;
+
+pub const MAX_EDITION_MARKER_SIZE: usize = 32;
+
+pub const EDITION_MARKER_BIT_SIZE: u64 = 248;
+
+#[repr(C)]
+#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, Copy)]
+pub enum Key {
+    Uninitialized,
+    EditionV1,
+    MasterEditionV1,
+    ReservationListV1,
+    MetadataV1,
+    ReservationListV2,
+    MasterEditionV2,
+    EditionMarker,
+}
+
+impl Default for Key {
+    fn default() -> Self {
+        Key::Uninitialized
+    }
+}
+
+#[repr(C)]
+#[derive(BorshSerialize, BorshDeserialize, Default, PartialEq, Debug, Clone)]
+pub struct Data {
+    /// The name of the asset
+    pub name: String,
+    /// The symbol for the asset
+    pub symbol: String,
+    /// URI pointing to JSON representing the asset
+    pub uri: String,
+    /// Royalty basis points that goes to creators in secondary sales (0-10000)
+    pub seller_fee_basis_points: u16,
+    /// Array of creators, optional
+    pub creators: Option<Vec<Creator>>,
+}
+
+#[repr(C)]
+#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Default)]
+pub struct Metadata {
+    pub key: Key,
+    pub update_authority: Pubkey,
+    pub mint: Pubkey,
+    pub data: Data,
+    // Immutable, once flipped, all sales of this metadata are considered secondary.
+    pub primary_sale_happened: bool,
+    // Whether or not the data struct is mutable, default is not
+    pub is_mutable: bool,
+}
+
+impl Metadata {
+    pub fn from_account_info(a: &AccountInfo) -> Option<Metadata> {
+        try_from_slice_checked(&a.data.borrow_mut(), Key::MetadataV1, MAX_METADATA_LEN)
+    }
+}
+
+#[repr(C)]
+#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
+pub struct Creator {
+    pub address: Pubkey,
+    pub verified: bool,
+    // In percentages, NOT basis points ;) Watch out!
+    pub share: u8,
+}

+ 68 - 0
solana/modules/token_bridge/token-metadata/src/utils.rs

@@ -0,0 +1,68 @@
+use crate::{
+    state::{
+        Data,
+        Key,
+        Metadata,
+        EDITION,
+        EDITION_MARKER_BIT_SIZE,
+        MAX_CREATOR_LIMIT,
+        MAX_EDITION_LEN,
+        MAX_EDITION_MARKER_SIZE,
+        MAX_MASTER_EDITION_LEN,
+        MAX_METADATA_LEN,
+        MAX_NAME_LENGTH,
+        MAX_SYMBOL_LENGTH,
+        MAX_URI_LENGTH,
+        PREFIX,
+    },
+};
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+use solana_program::{
+    account_info::AccountInfo,
+    borsh::try_from_slice_unchecked,
+    entrypoint::ProgramResult,
+    msg,
+    program::{
+        invoke,
+        invoke_signed,
+    },
+    program_error::ProgramError,
+    program_option::COption,
+    program_pack::{
+        IsInitialized,
+        Pack,
+    },
+    pubkey::Pubkey,
+    system_instruction,
+    sysvar::{
+        rent::Rent,
+        Sysvar,
+    },
+};
+use spl_token::{
+    instruction::{
+        set_authority,
+        AuthorityType,
+    },
+    state::{
+        Account,
+        Mint,
+    },
+};
+use std::convert::TryInto;
+
+pub fn try_from_slice_checked<T: BorshDeserialize>(
+    data: &[u8],
+    data_type: Key,
+    data_size: usize,
+) -> Option<T> {
+    if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8)
+        || data.len() != data_size
+    {
+        return None;
+    }
+    try_from_slice_unchecked(data).ok()
+}

+ 0 - 70
solana/solitaire/program/src/macros.rs

@@ -102,76 +102,6 @@ macro_rules! solitaire {
     }
     }
 }
 }
 
 
-#[macro_export]
-macro_rules! data_wrapper {
-    ($name:ident, $embed:ty, $state:expr) => {
-        #[repr(transparent)]
-        pub struct $name<'b>(solitaire::Data<'b, $embed, { $state }>);
-
-        impl<'b> std::ops::Deref for $name<'b> {
-            type Target = solitaire::Data<'b, $embed, { $state }>;
-
-            fn deref(&self) -> &Self::Target {
-                return &self.0;
-            }
-        }
-
-        impl<'b> std::ops::DerefMut for $name<'b> {
-            fn deref_mut(&mut self) -> &mut Self::Target {
-                unsafe { std::mem::transmute(&mut self.0) }
-            }
-        }
-
-        impl<'a, 'b: 'a> solitaire::processors::keyed::Keyed<'a, 'b> for $name<'b> {
-            fn info(&'a self) -> &'a solitaire::Info<'b> {
-                self.0.info()
-            }
-        }
-
-        impl<'b> solitaire::AccountSize for $name<'b> {
-            fn size(&self) -> usize {
-                return self.0.size();
-            }
-        }
-
-        impl<'a, 'b: 'a, 'c> solitaire::Peel<'a, 'b, 'c> for $name<'b> {
-            fn peel<T>(ctx: &'c mut solitaire::Context<'a, 'b, 'c, T>) -> solitaire::Result<Self>
-            where
-                Self: Sized,
-            {
-                solitaire::Data::peel(ctx).map(|v| $name(v))
-            }
-
-            fn deps() -> Vec<solana_program::pubkey::Pubkey> {
-                solitaire::Data::<'_, $embed, { $state }>::deps()
-            }
-
-            fn persist(
-                &self,
-                program_id: &solana_program::pubkey::Pubkey,
-            ) -> solitaire::Result<()> {
-                solitaire::Data::<'_, $embed, { $state }>::persist(self, program_id)
-            }
-        }
-
-        impl<'b> solitaire::Owned for $name<'b> {
-            fn owner(&self) -> solitaire::AccountOwner {
-                return self.1.owner();
-            }
-        }
-
-        #[cfg(feature = "client")]
-        impl<'b> solitaire_client::Wrap for $name<'b> {
-            fn wrap(
-                a: &solitaire_client::AccEntry,
-            ) -> std::result::Result<Vec<solitaire_client::AccountMeta>, solitaire_client::ErrBox>
-            {
-                solitaire::Data::<'b, $embed, { $state }>::wrap(a)
-            }
-        }
-    };
-}
-
 #[macro_export]
 #[macro_export]
 macro_rules! pack_type {
 macro_rules! pack_type {
     ($name:ident, $embed:ty, $owner:expr) => {
     ($name:ident, $embed:ty, $owner:expr) => {