Skip to content

Commit 518f189

Browse files
committed
solana/token_bridge: relax length requirement in unpacking token2022 mints
to allow for extensions
1 parent 8641e5f commit 518f189

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
lines changed

solana/modules/token_bridge/program/src/types.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ use serde::{
66
Deserialize,
77
Serialize,
88
};
9-
use solana_program::pubkey::Pubkey;
9+
use solana_program::{
10+
program_error::ProgramError,
11+
program_pack::{
12+
IsInitialized,
13+
Pack,
14+
},
15+
pubkey::Pubkey,
16+
};
1017
use solitaire::{
1118
pack_type,
1219
processors::seeded::{
@@ -101,10 +108,43 @@ pub mod spl_token_2022 {
101108
}
102109
}
103110

111+
trait MintExtensionPack: Pack {
112+
fn unpack(input: &[u8]) -> Result<Self, ProgramError>;
113+
}
114+
115+
// from: https://github.com/solana-program/token-2022/blob/9cbf7a1e9bab57aabb71b4b84fc84e3670108573/interface/src/extension/mod.rs#L262-L268
116+
// Since there is no account discriminator in these accounts, it's possible to confuse a multisig account for a mint account.
117+
// This check prevents that by ensuring the length is not equal to a multisig account length.
118+
// Mint accounts that happen to be 355 bytes long are out of luck (but this won't concern us, if it's even possible).
119+
fn check_min_len_and_not_multisig(input: &[u8], minimum_len: usize) -> Result<(), ProgramError> {
120+
const MULTISIG_LEN: usize = 355; // spl_token::state::Multisig::LEN;
121+
if input.len() == MULTISIG_LEN || input.len() < minimum_len {
122+
Err(ProgramError::InvalidAccountData)
123+
} else {
124+
Ok(())
125+
}
126+
}
127+
128+
impl MintExtensionPack for Mint {
129+
// this implementation is almost identical to the default Pack::unpack,
130+
// except for the length check. Instead of requiring exact length, we require
131+
// a minimum length, to allow for extensions.
132+
fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
133+
check_min_len_and_not_multisig(input, Self::LEN)?;
134+
let value: Mint = solana_program::program_pack::Pack::unpack_from_slice(input)?;
135+
if value.is_initialized() {
136+
Ok(value)
137+
} else {
138+
Err(ProgramError::UninitializedAccount)
139+
}
140+
}
141+
}
142+
104143
pack_type!(
105144
SplMint,
106145
Mint,
107-
AccountOwner::OneOf(vec![spl_token::id(), spl_token_2022::id()])
146+
AccountOwner::OneOf(vec![spl_token::id(), spl_token_2022::id()]),
147+
MintExtensionPack
108148
);
109149
pack_type!(
110150
SplAccount,

solana/solitaire/program/src/macros.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,26 @@ macro_rules! solitaire {
111111

112112
#[macro_export]
113113
macro_rules! pack_type_impl {
114-
($name:ident, $embed:ty, $owner:expr) => {
114+
// We take a "unpacker" as an input, which specifies how to unpack the embedded type.
115+
// In most cases, this should be just be
116+
// `solana_program::program_pack::Pack`, but in some cases (like token-2022
117+
// mints) it may be a custom trait that provides an `unpack` method. This is
118+
// because `Pack` does a strict length check on the account, whereas
119+
// token-2022 mints with extensions might be longer.
120+
//
121+
// NOTE: we only use this on the deserialisation side, but we keep the call for serialisation
122+
// as solana_program::program_pack::Pack::pack_into_slice. We could generalise that side too, but in
123+
// reality, that code is never invoked, because solitaire will persist (and
124+
// thus serialise) accounts that are owned by the current program.
125+
// `pack_type!` on the other hands is only used for solitaire-ising external accounts.
126+
($name:ident, $embed:ty, $owner:expr, $unpacker:path) => {
115127
#[repr(transparent)]
116128
pub struct $name(pub $embed);
117129

118130
impl BorshDeserialize for $name {
119131
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
120132
let acc = $name(
121-
solana_program::program_pack::Pack::unpack(buf)
133+
<$embed as $unpacker>::unpack(buf)
122134
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
123135
);
124136
// We need to clear the buf to show to Borsh that we've read all data
@@ -162,15 +174,21 @@ macro_rules! pack_type_impl {
162174
#[macro_export]
163175
macro_rules! pack_type {
164176
($name:ident, $embed:ty, AccountOwner::OneOf($owner:expr)) => {
165-
solitaire::pack_type_impl!($name, $embed, AccountOwner::OneOf($owner));
177+
solitaire::pack_type_impl!($name, $embed, AccountOwner::OneOf($owner), solana_program::program_pack::Pack);
166178

167179
impl solitaire::processors::seeded::MultiOwned for $name {
168180
}
169181
};
170182
($name:ident, $embed:ty, $owner:expr) => {
171-
solitaire::pack_type_impl!($name, $embed, $owner);
183+
solitaire::pack_type_impl!($name, $embed, $owner, solana_program::program_pack::Pack);
172184

173185
impl solitaire::processors::seeded::SingleOwned for $name {
174186
}
175187
};
188+
($name:ident, $embed:ty, AccountOwner::OneOf($owner:expr), $unpacker:ident) => {
189+
solitaire::pack_type_impl!($name, $embed, AccountOwner::OneOf($owner), $unpacker);
190+
191+
impl solitaire::processors::seeded::MultiOwned for $name {
192+
}
193+
};
176194
}

0 commit comments

Comments
 (0)