solana/token_bridge: allow updating metadata
Change-Id: I34872eaa9e931d9698808cc658840a5885c3dc5f
This commit is contained in:
parent
55bc96d979
commit
124efece2a
|
@ -13,7 +13,10 @@ use crate::{
|
||||||
},
|
},
|
||||||
messages::PayloadAssetMeta,
|
messages::PayloadAssetMeta,
|
||||||
types::*,
|
types::*,
|
||||||
TokenBridgeError::InvalidChain,
|
TokenBridgeError::{
|
||||||
|
InvalidChain,
|
||||||
|
InvalidMetadata,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use bridge::{
|
use bridge::{
|
||||||
vaa::ClaimableVAA,
|
vaa::ClaimableVAA,
|
||||||
|
@ -40,6 +43,10 @@ use spl_token::{
|
||||||
Mint,
|
Mint,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use spl_token_metadata::state::{
|
||||||
|
Data as SplData,
|
||||||
|
Metadata,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
ops::{
|
ops::{
|
||||||
|
@ -57,8 +64,8 @@ pub struct CreateWrapped<'b> {
|
||||||
pub vaa: ClaimableVAA<'b, PayloadAssetMeta>,
|
pub vaa: ClaimableVAA<'b, PayloadAssetMeta>,
|
||||||
|
|
||||||
// New Wrapped
|
// New Wrapped
|
||||||
pub mint: Mut<WrappedMint<'b, { AccountState::Uninitialized }>>,
|
pub mint: Mut<WrappedMint<'b, { AccountState::MaybeInitialized }>>,
|
||||||
pub meta: Mut<WrappedTokenMeta<'b, { AccountState::Uninitialized }>>,
|
pub meta: Mut<WrappedTokenMeta<'b, { AccountState::MaybeInitialized }>>,
|
||||||
|
|
||||||
/// SPL Metadata for the associated Mint
|
/// SPL Metadata for the associated Mint
|
||||||
pub spl_metadata: Mut<SplTokenMeta<'b>>,
|
pub spl_metadata: Mut<SplTokenMeta<'b>>,
|
||||||
|
@ -103,8 +110,6 @@ pub fn create_wrapped(
|
||||||
accs: &mut CreateWrapped,
|
accs: &mut CreateWrapped,
|
||||||
data: CreateWrappedData,
|
data: CreateWrappedData,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use bstr::ByteSlice;
|
|
||||||
|
|
||||||
// Do not process attestations sourced from the current chain.
|
// Do not process attestations sourced from the current chain.
|
||||||
if accs.vaa.token_chain == CHAIN_ID_SOLANA {
|
if accs.vaa.token_chain == CHAIN_ID_SOLANA {
|
||||||
return Err(InvalidChain.into());
|
return Err(InvalidChain.into());
|
||||||
|
@ -125,6 +130,18 @@ pub fn create_wrapped(
|
||||||
accs.vaa.verify(ctx.program_id)?;
|
accs.vaa.verify(ctx.program_id)?;
|
||||||
accs.vaa.claim(ctx, accs.payer.key)?;
|
accs.vaa.claim(ctx, accs.payer.key)?;
|
||||||
|
|
||||||
|
if accs.mint.is_initialized() {
|
||||||
|
update_accounts(ctx, accs, data)
|
||||||
|
} else {
|
||||||
|
create_accounts(ctx, accs, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_accounts(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut CreateWrapped,
|
||||||
|
data: CreateWrappedData,
|
||||||
|
) -> Result<()> {
|
||||||
// Create mint account
|
// Create mint account
|
||||||
accs.mint
|
accs.mint
|
||||||
.create(&((&*accs).into()), ctx, accs.payer.key, Exempt)?;
|
.create(&((&*accs).into()), ctx, accs.payer.key, Exempt)?;
|
||||||
|
@ -151,18 +168,9 @@ pub fn create_wrapped(
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut name = accs.vaa.name.clone().as_bytes().to_vec();
|
// Normalize Token Metadata.
|
||||||
name.truncate(32 - 11);
|
let name = truncate_utf8(&accs.vaa.name, 32 - 11) + " (Wormhole)";
|
||||||
let mut name: Vec<char> = name.chars().collect();
|
let symbol = truncate_utf8(&accs.vaa.symbol, 10);
|
||||||
name.retain(|&c| c != '\u{FFFD}');
|
|
||||||
let mut name: String = name.iter().collect();
|
|
||||||
name += " (Wormhole)";
|
|
||||||
|
|
||||||
let mut symbol = accs.vaa.symbol.clone().as_bytes().to_vec();
|
|
||||||
symbol.truncate(10);
|
|
||||||
let mut symbol: Vec<char> = symbol.chars().collect();
|
|
||||||
symbol.retain(|&c| c != '\u{FFFD}');
|
|
||||||
let symbol: String = symbol.iter().collect();
|
|
||||||
|
|
||||||
let spl_token_metadata_ix = spl_token_metadata::instruction::create_metadata_accounts(
|
let spl_token_metadata_ix = spl_token_metadata::instruction::create_metadata_accounts(
|
||||||
spl_token_metadata::id(),
|
spl_token_metadata::id(),
|
||||||
|
@ -188,3 +196,96 @@ pub fn create_wrapped(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_accounts(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut CreateWrapped,
|
||||||
|
data: CreateWrappedData,
|
||||||
|
) -> Result<()> {
|
||||||
|
accs.spl_metadata.verify_derivation(
|
||||||
|
&spl_token_metadata::id(),
|
||||||
|
&SplTokenMetaDerivationData {
|
||||||
|
mint: *accs.mint.info().key,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut metadata: SplData = Metadata::from_account_info(accs.spl_metadata.info())
|
||||||
|
.ok_or(InvalidMetadata)?
|
||||||
|
.data;
|
||||||
|
|
||||||
|
// Normalize token metadata.
|
||||||
|
metadata.name = truncate_utf8(&accs.vaa.name, 32 - 11) + " (Wormhole)";
|
||||||
|
metadata.symbol = truncate_utf8(&accs.vaa.symbol, 10);
|
||||||
|
|
||||||
|
// Update SPL Metadata
|
||||||
|
let spl_token_metadata_ix = spl_token_metadata::instruction::update_metadata_accounts(
|
||||||
|
spl_token_metadata::id(),
|
||||||
|
*accs.spl_metadata.key,
|
||||||
|
*accs.mint_authority.info().key,
|
||||||
|
None,
|
||||||
|
Some(metadata),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
invoke_seeded(&spl_token_metadata_ix, ctx, &accs.mint_authority, None)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte-truncates potentially invalid UTF-8 encoded strings by converting to Unicode codepoints and
|
||||||
|
// stripping unrecognised characters.
|
||||||
|
pub fn truncate_utf8(data: impl AsRef<[u8]>, len: usize) -> String {
|
||||||
|
use bstr::ByteSlice;
|
||||||
|
let mut data = data.as_ref().to_vec();
|
||||||
|
data.truncate(len);
|
||||||
|
let mut data: Vec<char> = data.chars().collect();
|
||||||
|
data.retain(|&c| c != '\u{FFFD}');
|
||||||
|
data.iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
fn extend_string(n: &str) -> Vec<u8> {
|
||||||
|
let mut bytes = vec![0u8; 32];
|
||||||
|
for i in 0..n.len() {
|
||||||
|
bytes[i] = n.as_bytes()[i];
|
||||||
|
}
|
||||||
|
bytes.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unicode_truncation() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let pairs = [
|
||||||
|
// Empty string should not error or mutate.
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
),
|
||||||
|
// Unicode < 32 should not be corrupted.
|
||||||
|
(
|
||||||
|
"🔥",
|
||||||
|
"🔥"
|
||||||
|
),
|
||||||
|
// Unicode @ 32 should not be corrupted.
|
||||||
|
(
|
||||||
|
"🔥🔥🔥🔥🔥🔥🔥🔥",
|
||||||
|
"🔥🔥🔥🔥🔥🔥🔥🔥"
|
||||||
|
),
|
||||||
|
// Unicode > 32 should be truncated correctly.
|
||||||
|
(
|
||||||
|
"🔥🔥🔥🔥🔥🔥🔥🔥🔥",
|
||||||
|
"🔥🔥🔥🔥🔥🔥🔥🔥"
|
||||||
|
),
|
||||||
|
// Partially overflowing Unicode > 32 should be removed.
|
||||||
|
// Note: Expecting 31 bytes.
|
||||||
|
(
|
||||||
|
"0000000000000000000000000000000🔥",
|
||||||
|
"0000000000000000000000000000000"
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, expected) in pairs {
|
||||||
|
assert_eq!(expected, super::truncate_utf8(input, 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue