Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit b1d2d98

Browse files
authored
token-metadata: Implement example program (#4546)
* token-metadata: Add example program * Implement UpdateField * Implement RemoveKey * Implement UpdateAuthority * Implement Emit * Move serialize helper to TLV * Add CI job * Remove re-export * Fix merge error
1 parent 9df673b commit b1d2d98

File tree

19 files changed

+1912
-19
lines changed

19 files changed

+1912
-19
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Token-Metadata Pull Request
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'token-metadata/**'
7+
- 'token/program-2022/**'
8+
- 'ci/*-version.sh'
9+
- '.github/workflows/pull-request-token-metadata.yml'
10+
push:
11+
branches: [master]
12+
paths:
13+
- 'token-metadata/**'
14+
- 'token/program-2022/**'
15+
- 'ci/*-version.sh'
16+
- '.github/workflows/pull-request-token-metadata.yml'
17+
18+
jobs:
19+
cargo-test-sbf:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v2
23+
24+
- name: Set env vars
25+
run: |
26+
source ci/rust-version.sh
27+
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
28+
source ci/solana-version.sh
29+
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
30+
31+
- uses: actions-rs/toolchain@v1
32+
with:
33+
toolchain: ${{ env.RUST_STABLE }}
34+
override: true
35+
profile: minimal
36+
37+
- uses: actions/cache@v2
38+
with:
39+
path: |
40+
~/.cargo/registry
41+
~/.cargo/git
42+
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
43+
44+
- uses: actions/cache@v2
45+
with:
46+
path: |
47+
~/.cargo/bin/rustfilt
48+
key: cargo-sbf-bins-${{ runner.os }}
49+
50+
- uses: actions/cache@v2
51+
with:
52+
path: ~/.cache/solana
53+
key: solana-${{ env.SOLANA_VERSION }}
54+
55+
- name: Install dependencies
56+
run: |
57+
./ci/install-build-deps.sh
58+
./ci/install-program-deps.sh
59+
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
60+
61+
- name: Build and test example
62+
run: ./ci/cargo-test-sbf.sh token-metadata/example

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ members = [
4040
"stateless-asks/program",
4141
"token-lending/cli",
4242
"token-lending/program",
43+
"token-metadata/example",
4344
"token-metadata/interface",
4445
"token-swap/program",
4546
"token-swap/program/fuzz",

libraries/type-length-value/src/state.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,54 @@ impl<'a> TlvState for TlvStateMut<'a> {
408408
}
409409
}
410410

411+
/// Packs a borsh-serializable value into an existing TLV space, reallocating
412+
/// the account and TLV as needed to accommodate for any change in space
413+
#[cfg(feature = "borsh")]
414+
pub fn realloc_and_borsh_serialize<V: SplDiscriminate + borsh::BorshSerialize>(
415+
account_info: &solana_program::account_info::AccountInfo,
416+
value: &V,
417+
) -> Result<(), ProgramError> {
418+
let previous_length = {
419+
let data = account_info.try_borrow_data()?;
420+
let TlvIndices {
421+
type_start: _,
422+
length_start,
423+
value_start,
424+
} = get_indices(&data, V::SPL_DISCRIMINATOR, false)?;
425+
usize::try_from(*pod_from_bytes::<Length>(&data[length_start..value_start])?)?
426+
};
427+
let new_length = solana_program::borsh::get_instance_packed_len(&value)?;
428+
let previous_account_size = account_info.try_data_len()?;
429+
if previous_length < new_length {
430+
// size increased, so realloc the account, then the TLV entry, then write data
431+
let additional_bytes = new_length
432+
.checked_sub(previous_length)
433+
.ok_or(ProgramError::AccountDataTooSmall)?;
434+
account_info.realloc(previous_account_size.saturating_add(additional_bytes), true)?;
435+
let mut buffer = account_info.try_borrow_mut_data()?;
436+
let mut state = TlvStateMut::unpack(&mut buffer)?;
437+
state.realloc::<V>(new_length)?;
438+
state.borsh_serialize(value)?;
439+
} else {
440+
// do it backwards otherwise, write the state, realloc TLV, then the account
441+
let mut buffer = account_info.try_borrow_mut_data()?;
442+
let mut state = TlvStateMut::unpack(&mut buffer)?;
443+
state.borsh_serialize(value)?;
444+
let removed_bytes = previous_length
445+
.checked_sub(new_length)
446+
.ok_or(ProgramError::AccountDataTooSmall)?;
447+
if removed_bytes > 0 {
448+
// we decreased the size, so need to realloc the TLV, then the account
449+
state.realloc::<V>(new_length)?;
450+
// this is probably fine, but be safe and avoid invalidating references
451+
drop(state);
452+
drop(buffer);
453+
account_info.realloc(previous_account_size.saturating_sub(removed_bytes), false)?;
454+
}
455+
}
456+
Ok(())
457+
}
458+
411459
/// Get the base size required for TLV data
412460
const fn get_base_len() -> usize {
413461
let indices = get_indices_unchecked(0);

token-metadata/example/Cargo.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "spl-token-metadata-example"
3+
version = "0.1.0"
4+
description = "Solana Program Library Token Metadata Example Program"
5+
authors = ["Solana Labs Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2021"
9+
10+
[features]
11+
no-entrypoint = []
12+
test-sbf = []
13+
14+
[dependencies]
15+
solana-program = "1.14.12"
16+
spl-type-length-value = { version = "0.1.0" , path = "../../libraries/type-length-value" }
17+
spl-token-2022 = { version = "0.6.0", path = "../../token/program-2022" }
18+
spl-token-metadata-interface = { version = "0.1.0", path = "../interface" }
19+
20+
[dev-dependencies]
21+
solana-program-test = "1.14.12"
22+
solana-sdk = "1.14.12"
23+
spl-token-client = { version = "0.5", path = "../../token/client" }
24+
test-case = "3.1"
25+
26+
[lib]
27+
crate-type = ["cdylib", "lib"]
28+
29+
[package.metadata.docs.rs]
30+
targets = ["x86_64-unknown-linux-gnu"]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//! Program entrypoint
2+
3+
use {
4+
crate::processor,
5+
solana_program::{
6+
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
7+
program_error::PrintProgramError, pubkey::Pubkey,
8+
},
9+
spl_token_metadata_interface::error::TokenMetadataError,
10+
};
11+
12+
entrypoint!(process_instruction);
13+
fn process_instruction(
14+
program_id: &Pubkey,
15+
accounts: &[AccountInfo],
16+
instruction_data: &[u8],
17+
) -> ProgramResult {
18+
if let Err(error) = processor::process(program_id, accounts, instruction_data) {
19+
// catch the error so we can print it
20+
error.print::<TokenMetadataError>();
21+
return Err(error);
22+
}
23+
Ok(())
24+
}

token-metadata/example/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! Crate defining an example program for storing SPL token metadata
2+
3+
#![allow(clippy::integer_arithmetic)]
4+
#![deny(missing_docs)]
5+
#![cfg_attr(not(test), forbid(unsafe_code))]
6+
7+
pub mod processor;
8+
9+
#[cfg(not(feature = "no-entrypoint"))]
10+
mod entrypoint;

0 commit comments

Comments
 (0)