Skip to content

Commit f62a0b0

Browse files
authored
interface: Add new instructions (#153)
* Add new instructions * Fix program id check * Use slice
1 parent 2c1b6ee commit f62a0b0

1 file changed

Lines changed: 240 additions & 1 deletion

File tree

interface/src/instruction.rs

Lines changed: 240 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub const MAX_SIGNERS: usize = 11;
1818
const U64_BYTES: usize = 8;
1919

2020
/// Instructions supported by the token program.
21-
#[repr(C)]
21+
#[repr(C, u8)]
2222
#[derive(Clone, Debug, PartialEq)]
2323
pub enum TokenInstruction<'a> {
2424
/// Initializes a new mint and optionally deposits all the newly minted
@@ -474,6 +474,78 @@ pub enum TokenInstruction<'a> {
474474
/// The `ui_amount` of tokens to reformat.
475475
ui_amount: &'a str,
476476
},
477+
/// This instruction is to be used to rescue SOL sent to any `TokenProgram`
478+
/// owned account by sending them to any other account, leaving behind only
479+
/// lamports for rent exemption.
480+
///
481+
/// Accounts expected by this instruction:
482+
///
483+
/// * Single owner/delegate
484+
/// 0. `[writable]` The source account.
485+
/// 1. `[writable]` The destination account.
486+
/// 2. `[signer]` The source account's owner/delegate.
487+
///
488+
/// * Multisignature owner/delegate
489+
/// 0. `[writable]` The source account.
490+
/// 1. `[writable]` The destination account.
491+
/// 2. `[]` The source account's multisignature owner/delegate.
492+
/// 3. `..+M` `[signer]` M signer accounts.
493+
WithdrawExcessLamports = 38,
494+
/// Transfer lamports from a native SOL account to a destination account.
495+
///
496+
/// This is useful to unwrap lamports from a wrapped SOL account.
497+
///
498+
/// Accounts expected by this instruction:
499+
///
500+
/// * Single owner/delegate
501+
/// 0. `[writable]` The source account.
502+
/// 1. `[writable]` The destination account.
503+
/// 2. `[signer]` The source account's owner/delegate.
504+
///
505+
/// * Multisignature owner/delegate
506+
/// 0. `[writable]` The source account.
507+
/// 1. `[writable]` The destination account.
508+
/// 2. `[]` The source account's multisignature owner/delegate.
509+
/// 3. `..+M` `[signer]` M signer accounts.
510+
///
511+
/// Data expected by this instruction:
512+
///
513+
/// - `Option<u64>` The amount of lamports to transfer. When an amount is
514+
/// not specified, the entire balance of the source account will be
515+
/// transferred.
516+
UnwrapLamports {
517+
/// The amount of lamports to transfer.
518+
amount: COption<u64>,
519+
} = 45,
520+
/// Executes a batch of instructions. The instructions to be executed are
521+
/// specified in sequence on the instruction data. Each instruction
522+
/// provides:
523+
/// - `u8`: number of accounts
524+
/// - `u8`: instruction data length (includes the discriminator)
525+
/// - `u8`: instruction discriminator
526+
/// - `[u8]`: instruction data
527+
///
528+
/// Accounts follow a similar pattern, where accounts for each instruction
529+
/// are specified in sequence. Therefore, the number of accounts
530+
/// expected by this instruction is variable, i.e., it depends on the
531+
/// instructions provided.
532+
///
533+
/// Both the number of accounts and instruction data length are used to
534+
/// identify the slice of accounts and instruction data for each
535+
/// instruction. Since the instruction data length is specified as a `u8`,
536+
/// the maximum instruction data length is 255 bytes, which is sufficient
537+
/// for all current instructions in the program.
538+
///
539+
/// When one or more batched instructions write return data, the batch
540+
/// instruction returns the data written by the last instruction that does
541+
/// so. That instruction does not have to be the final instruction in the
542+
/// batch: later instructions that do not write return data leave the last
543+
/// written return data unchanged.
544+
///
545+
/// Note that it is not sound to have a `batch` instruction that contains
546+
/// other `batch` instruction; an error will be raised when this is
547+
/// detected.
548+
Batch = 255,
477549
// Any new variants also need to be added to program-2022 `TokenInstruction`, so that the
478550
// latter remains a superset of this instruction set. New variants also need to be added to
479551
// token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
@@ -580,6 +652,12 @@ impl<'a> TokenInstruction<'a> {
580652
let ui_amount = std::str::from_utf8(rest).map_err(|_| InvalidInstruction)?;
581653
Self::UiAmountToAmount { ui_amount }
582654
}
655+
38 => Self::WithdrawExcessLamports,
656+
45 => {
657+
let (amount, _rest) = Self::unpack_u64_option(rest)?;
658+
Self::UnwrapLamports { amount }
659+
}
660+
255 => Self::Batch,
583661
_ => return Err(TokenError::InvalidInstruction.into()),
584662
})
585663
}
@@ -691,6 +769,16 @@ impl<'a> TokenInstruction<'a> {
691769
buf.push(24);
692770
buf.extend_from_slice(ui_amount.as_bytes());
693771
}
772+
&Self::WithdrawExcessLamports => {
773+
buf.push(38);
774+
}
775+
Self::UnwrapLamports { ref amount } => {
776+
buf.push(45);
777+
Self::pack_u64_option(amount, &mut buf);
778+
}
779+
&Self::Batch => {
780+
buf.push(255);
781+
}
694782
};
695783
buf
696784
}
@@ -736,6 +824,30 @@ impl<'a> TokenInstruction<'a> {
736824
Ok((value, &input[U64_BYTES..]))
737825
}
738826

827+
fn unpack_u64_option(input: &[u8]) -> Result<(COption<u64>, &[u8]), ProgramError> {
828+
match input.split_first() {
829+
Option::Some((&0, rest)) => Ok((COption::None, rest)),
830+
Option::Some((&1, rest)) if rest.len() >= 8 => {
831+
let (amount, rest) = rest
832+
.split_first_chunk::<8>()
833+
.ok_or(TokenError::InvalidInstruction)?;
834+
let v = u64::from_le_bytes(*amount);
835+
Ok((COption::Some(v), rest))
836+
}
837+
_ => Err(TokenError::InvalidInstruction.into()),
838+
}
839+
}
840+
841+
fn pack_u64_option(value: &COption<u64>, buf: &mut Vec<u8>) {
842+
match *value {
843+
COption::Some(ref amount) => {
844+
buf.push(1);
845+
buf.extend_from_slice(&amount.to_le_bytes());
846+
}
847+
COption::None => buf.push(0),
848+
}
849+
}
850+
739851
fn unpack_amount_decimals(input: &[u8]) -> Result<(u64, u8, &[u8]), ProgramError> {
740852
let (amount, rest) = Self::unpack_u64(input)?;
741853
let (&decimals, rest) = rest.split_first().ok_or(TokenError::InvalidInstruction)?;
@@ -1450,6 +1562,101 @@ pub fn ui_amount_to_amount(
14501562
})
14511563
}
14521564

1565+
/// Creates a `WithdrawExcessLamports` instruction
1566+
pub fn withdraw_excess_lamports(
1567+
token_program_id: &Pubkey,
1568+
account_pubkey: &Pubkey,
1569+
destination_pubkey: &Pubkey,
1570+
authority_pubkey: &Pubkey,
1571+
signer_pubkeys: &[&Pubkey],
1572+
) -> Result<Instruction, ProgramError> {
1573+
check_program_account(token_program_id)?;
1574+
let data = TokenInstruction::WithdrawExcessLamports.pack();
1575+
1576+
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
1577+
accounts.push(AccountMeta::new(*account_pubkey, false));
1578+
accounts.push(AccountMeta::new(*destination_pubkey, false));
1579+
accounts.push(AccountMeta::new_readonly(
1580+
*authority_pubkey,
1581+
signer_pubkeys.is_empty(),
1582+
));
1583+
for signer_pubkey in signer_pubkeys.iter() {
1584+
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
1585+
}
1586+
1587+
Ok(Instruction {
1588+
program_id: *token_program_id,
1589+
accounts,
1590+
data,
1591+
})
1592+
}
1593+
1594+
/// Creates a `UnwrapLamports` instruction
1595+
pub fn unwrap_lamports(
1596+
token_program_id: &Pubkey,
1597+
account_pubkey: &Pubkey,
1598+
destination_pubkey: &Pubkey,
1599+
authority_pubkey: &Pubkey,
1600+
signer_pubkeys: &[&Pubkey],
1601+
amount: Option<u64>,
1602+
) -> Result<Instruction, ProgramError> {
1603+
check_program_account(token_program_id)?;
1604+
1605+
let amount = amount.into();
1606+
let data = TokenInstruction::UnwrapLamports { amount }.pack();
1607+
1608+
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
1609+
accounts.push(AccountMeta::new(*account_pubkey, false));
1610+
accounts.push(AccountMeta::new(*destination_pubkey, false));
1611+
accounts.push(AccountMeta::new_readonly(
1612+
*authority_pubkey,
1613+
signer_pubkeys.is_empty(),
1614+
));
1615+
for signer_pubkey in signer_pubkeys.iter() {
1616+
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
1617+
}
1618+
1619+
Ok(Instruction {
1620+
program_id: *token_program_id,
1621+
accounts,
1622+
data,
1623+
})
1624+
}
1625+
1626+
/// Creates a `Batch` instruction
1627+
pub fn batch(
1628+
token_program_id: &Pubkey,
1629+
instructions: &[Instruction],
1630+
) -> Result<Instruction, ProgramError> {
1631+
check_program_account(token_program_id)?;
1632+
1633+
let mut data: Vec<u8> = TokenInstruction::Batch.pack();
1634+
let mut accounts: Vec<AccountMeta> = vec![];
1635+
1636+
for instruction in instructions {
1637+
if token_program_id != &instruction.program_id {
1638+
return Err(ProgramError::IncorrectProgramId);
1639+
}
1640+
1641+
data.push(instruction.accounts.len() as u8);
1642+
1643+
if instruction.data.len() > u8::MAX as usize {
1644+
return Err(ProgramError::InvalidInstructionData);
1645+
}
1646+
1647+
data.push(instruction.data.len() as u8);
1648+
1649+
data.extend_from_slice(&instruction.data);
1650+
accounts.extend_from_slice(&instruction.accounts);
1651+
}
1652+
1653+
Ok(Instruction {
1654+
program_id: *token_program_id,
1655+
data,
1656+
accounts,
1657+
})
1658+
}
1659+
14531660
/// Utility function that checks index is between `MIN_SIGNERS` and
14541661
/// `MAX_SIGNERS`
14551662
pub fn is_valid_signer_index(index: usize) -> bool {
@@ -1699,6 +1906,38 @@ mod test {
16991906
assert_eq!(packed, expect);
17001907
let unpacked = TokenInstruction::unpack(&expect).unwrap();
17011908
assert_eq!(unpacked, check);
1909+
1910+
let check = TokenInstruction::WithdrawExcessLamports;
1911+
let packed = check.pack();
1912+
let expect = vec![38u8];
1913+
assert_eq!(packed, expect);
1914+
let unpacked = TokenInstruction::unpack(&expect).unwrap();
1915+
assert_eq!(unpacked, check);
1916+
1917+
let check = TokenInstruction::UnwrapLamports {
1918+
amount: COption::Some(42),
1919+
};
1920+
let packed = check.pack();
1921+
let expect = vec![45u8, 1, 42, 0, 0, 0, 0, 0, 0, 0];
1922+
assert_eq!(packed, expect);
1923+
let unpacked = TokenInstruction::unpack(&expect).unwrap();
1924+
assert_eq!(unpacked, check);
1925+
1926+
let check = TokenInstruction::UnwrapLamports {
1927+
amount: COption::None,
1928+
};
1929+
let packed = check.pack();
1930+
let expect = vec![45u8, 0];
1931+
assert_eq!(packed, expect);
1932+
let unpacked = TokenInstruction::unpack(&expect).unwrap();
1933+
assert_eq!(unpacked, check);
1934+
1935+
let check = TokenInstruction::Batch;
1936+
let packed = check.pack();
1937+
let expect = vec![255u8];
1938+
assert_eq!(packed, expect);
1939+
let unpacked = TokenInstruction::unpack(&expect).unwrap();
1940+
assert_eq!(unpacked, check);
17021941
}
17031942

17041943
#[test]

0 commit comments

Comments
 (0)