Solana Programs Part 1: Understanding SPL Token Mint
March 21, 2022

Solana token program (source code) is among the most frequently executed Solana smart contracts. Its program id: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

  • Most user-deployed Solana smart contracts (directly or transitively) use the token program to mint/transfer/burn tokens (i.e., SPL tokens). For example, if your Solana project is a decentralised exchange, a stable coin, an ICO, or a cross-chain bridge, you are likely relying on the token program.
  • SPL tokens are similar to ERC20/ERC721 tokens, but with tricky differences.

In this article, we elaborate on the SPL tokens and introduce the internals of those most commonly used instructions in the token program:

Instruction SPL Function
InitializeMint spl_token::instruction::initialize_mint
InitializeAccount spl_token::instruction::initialize_account
MintTo spl_token::instruction::mint_to
Burn spl_token::instruction::burn
Transfer spl_token::instruction::transfer
SyncNative spl_token::instruction::sync_native
Approve spl_token::instruction::approve
Revoke spl_token::instruction::revoke
FreezeAccount spl_token::instruction::freeze_account
ThawAccount spl_token::instruction::thaw_account
CloseAccount spl_token::instruction::close_account
SetAuthority spl_token::instruction::set_authority

These instructions are implemented with three important data structures:

  • Mint — data type of every mint account (e.g. mint_info.data)
  • Account — data type of every token account (e.g. source_account_info.data)
  • Multisig — data type of every multisig account (e.g. owner_account_info.data)

InitializeMint

The InitializeMint instruction is the first instruction to call before anything else in the SPL token mint process. It will invoke the function _process_initialize_mint to initialize a mint account (on line 36 mint_info supplied as the first account in the accounts vector):

1*gvZGpBVIKR_9xkwfLrxVQQ.png (700×197)

The mint account’s data is deserialized into mint of the Mint data type, and then checked for re-initialization error:

1*JsFIMaSe7d9Ue-Ykif3z7Q.png (700×56)

The Mint struct has five attributes:

1*u1g1F3JOJQq7C69k6Mob2A.png (700×220)

Finally, the mint account is initialized by setting its mint_authority , decimals , is_initialized flag true, and an optional freeze_authority.

1*2XYXi9b_3mL0-mI45ZfHJg.png (700×103)

Note that in the initialization code above, the mint’s supply is not set but has a default value 0.

InitializeAccount

The instruction will invoke the function _process_initialize_account to initialize a token account (on line 90 new_account_info supplied as the first account in the accounts vector):

1*I_gLVjqLn-hZptn7824v5A.png (700×262)

Similar to InitializeMint, the token account’s data is deserialized into account of the Token data type, and then checked for re-initialization error:

1*7dvGosCsm_-spny1qcIoLg.png (700×49)

The Token struct has eight attributes:

1*iW2ZKPRMaJkINv6NGwQScQ.png (700×330)

Finally, the token account is initialized by setting its mint, owner, close_authority (default None), delegate (default None), delegated_amount (default 0), state (Initialized), is_native flag and amount(i.e. the amount of tokens this account holds):

1*GwF77C2Yr0fLTqlL1_M4Rw.png (700×288)
1*TLFhhnd3OPQk9DU4TpjvZA.png (700×17)

Note that when the token mint is the native mint, i.e., WSOL (the wrapped sol, program_id: So11111111111111111111111111111111111111112), is_native is true and amount is initialized to the amount of lamports (minus rent) in the token account.

MintTo

After a mint and a token account have been initialized, the MintTo instruction can be called to mint tokens by function process_mint_to:

1*isw0CQS2vxbWXMR741QS6Q.png (700×184)

The MintTo instruction takes three user-supplied accounts:

  • mint_info: the mint account (line 523)
  • destination_account_info : the token account to mint tokens to (line 524)
  • owner_info : the mint account’s authority (line 525)

It also takes two user-supplied instruction data:

  • amount : the amount of tokens to mint
  • expected_decimals : the expected mint decimal (for mint validation)

Note that there are several validity checks for the MintTo instruction:

  1. The destination account is not frozen and is not native
  2. The destination account’s mint is valid: it is the mint account’s key
  3. The destination account’s owner and the mint account’s owner are both the token program
  4. The transaction is signed by the mint account’s authority
1*sw3vJvjG1uGyENhSlAxRfA.png (700×151)

Finally, if the MintTo instruction is processed successfully, the destination account’s token amount (destination_account.amount) and the mint’s supply (mint.supply) will both be incremented by amount :

1*UROtwqzY7Co-dAXmx8IIpg.png (700×224)

Burn

The Burn instruction is (mostly) opposite to MintTo . It calls the function process_burn to burn a certain amount of tokens from a source token account:

1*tQs5QTrw--lfNQte8Imjug.png (700×214)

If the Burn instruction is successful, the source account’s token amount (source_account.amount) and the mint’s supply (mint.supply) will both be decremented by amount :

1*gMpCkjZOpK_VSRrWvnCLZw.png (700×232)

A main difference is that the signed authority account is not the mint’s authority, but the source account’s owner or delegate. For the latter, the logic is slightly complex:

  1. Check if the source account’s delegate matches the user-supplied authority account (line 617) and that account is signed (lines 618–623).
  2. Check if the burned amount is no more than the source account’s delegate_amount (line 625)
  3. Decrement the delegate_amount by the burned amount (lines 628–631), and if delegate_amount becomes zero then set source account’s delegate to None (lines 632–633).
1*R8Ei8-S-MoAnKVT_-BUxmg.png (700×261)

Transfer

The Transfer instruction will invoke the function process_transfer to transfer a certain amount of token from a source account to a destination account:

1*Ntyf-m894FCOWo6lgTOMBQ.png (700×174)
1*6l8GYN7GNnm6mvPnIyMOYg.png (700×37)

This function has several important validity checks:

  1. Neither source_account nor destination_account is frozen
  2. The source_account’s mint and destination_account’s mint are the same
  3. The transferred amount is no more than source_account’s token amount
  4. the authority (either the owner of source_account or its matched delegate) is signed
Note: source_account may be the same as destination_account . If that’s the case, then it is a self-transfer, and the function simply returns Ok. In other words, self-transfer is allowed by the token program.

Finally, if the Transfer instruction is successful, the source_account’s token amount is decremented by amount and the destination_account’s token amount is incremented by amount :

1*nlE29LMTjAtn0luonfeq8Q.png (700×196)

Note: For native token accounts (i.e., the token mint is WSOL), in order to transfer SOL directly using the token program, the token amount should be “sync-ed” with the amount of lamports in the account.

Thus, the lamports of source_account and destination_account must also be updated accordingly:

1*PrZYWIFdx1pW3QeAxd2cxQ.png (700×171)

SyncNative

For a native token account, when the token amount is less than the amount of lamports in the account (possibly caused by a system_instruction transfer of lamports to the token account), the token program has a SyncNative instruction to sync these two values:

1*TTU5f1Mvtm-4yD7-TJfMzA.png (700×292)

In the above, the native account’s amount is updated to the amount of lamports in the account minus rent (line 768).

Approve

The Approve instruction will invoke the function process_approve to set a delegate account for a source account and a delegate amount by the source account’s authority:

1*0odfrTCp2G1RePGq_UYjOw.png (700×172)
1*VuUrr3TYbCP6ljXG6e3_zg.png (700×47)

Note that a token account allows only one delegate at a time.

The Approve instruction will override the previous delegate and the delegate amount set by the last Approve instruction. Even if the previous delegate amount is not spent or only spent partially, it will still be overwritten.

Revoke

The Revoke instruction will call the function process_revoke to set a source account’s delegate to None and the delegate amount to 0:

1*8CaZ1fotX_kwSkkMuPyz1g.png (700×117)
1*sfxB_qfcF_z94O1yaRZGLw.png (700×69)

FreezeAccount/ThawAccount

The FreezeAccount/ThawAccount instructions will call the function process_toggle_freeze_account to set a source account’s state to Frozen/Initialized respectively:

1*alhFU5U4AQqQq_PsHWtZvg.png (700×174)
1*qgLEYsIzD2SsjMT7Q2UMWw.png (700×151)

Note that this instruction must be signed by the freeze_authority of the mint account (line 716 mint_info). It will fail if the mint’s free_authority is not set.

CloseAccount

The CloseAccount instruction will call the function process_close_account to close a token account (source_account) and transfer all its lamports to another token account (destination_account) by the source code’s authority:

1*6rc92OABks4Uu0DaKQL3sQ.png (700×85)

The close of a token account includes three steps:

  1. transfer all its lamports to a destination account (lines 695–697)
  2. set its lamports to zero (line 700)
  3. call sol_memset to clear the account’s data (line 702)
1*0jl3el6XSTMj3MVQuXjh3A.png (700×142)

Note that the source account must be not the same as the destination account:

1*CJKPWxY1g8M5s4wycOtfvA.png (700×34)

SetAuthority

The SetAuthority instruction is used to set a new authority for a mint or token account. The authority could be either a single account (wallet or PDA) or a Multisignature account (will elaborate shortly).

1*VZkuCKWwKLFBubCHiwvyvA.png (700×183)

The type of authority to set is specified by the authority_type . There are four different types:

  1. AuthorityType::AccountOwner : set a token account’s owner
  2. AuthorityType::CloseAccount : set a token account’s close_authority
  3. AuthorityType::MintTokens : set a mint account’s mint_authority
  4. AuthorityType::FreezeAccount : set a mint account’s free_authority

Multisig

A Multisig account has the Multisig data type with four attributes:

  • m — number of required signers
  • n —total number of valid signers
  • is_initialized — initialization flag
  • signers — an array of all valid signer pubkeys
1*A6u_24OeJqWWeiWDyAMJ3g.png (700×252)

A Multisig account can be initialized by the InitializeMultisig instruction (line 175 multisig_info account):

1*cZ4HwSTyVBTZzEfwt8ZThA.png (700×146)

In the initialization, m is passed as a parameter, n is the length of valid signer accounts (line 192 signer_infos supplied in the accounts vector):

1*V-AZRK0IzMIvhy_bTgG2pw.png (700×249)

We will continue to introduce the technical details of other popular Solana programs (e.g., token-swap, stake-pool, associated-token-account) in the next few articles.


Sec3 audit

Sec3 (formerly Soteria) is founded by leading minds in the fields of blockchain security and software verification.

We are pleased to provide audit services to high-impact Dapps on Solana. Please visit soteria.dev or email contact@soteria.dev