Search code examples
node.jsrustsolananearprotocol

The serialize of Pubkey are difference between borsh-js and borsh-rs


I serialize a pubkey (solana account publickey)in the js and rust with borsh, but the output in js with a length of 4 bytes,and the pubkey in js client is a length of 44bytes string,but it is a pubkey with 32bytes in rust program,without the 4 bytes length before it, How to serialize them as same?

In the JS:

class MintAccount extends Assignable { }
 console.log(MintAccount);
 const SCHEMA = new Map([
 [
   MintAccount,
   {
     kind: 'struct',
     fields: [
       ['tag', 'u8'],
       ['authority','string'],
       ['supply', 'u64']
     ],
   },
 ]
]);
console.log(feePayer.publicKey.toString());
let mintAccount = new MintAccount({tag:1,7xiHVn7EeQwbsPrAtaY3aokHekYTrURsgzReFxd1JeVR,supply:0})
let accountDataBuffer = borsh.serialize(SCHEMA, mintAccount);
console.log(accountDataBuffer);

and in the RUST: in states.rs

#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
pub enum AccountTag {
    Uninitialized,
    Mint,
    TokenAccount,
}

#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct Mint {
    pub tag: AccountTag,
    pub authority: Pubkey,
    pub supply: u64,
}

impl Mint {
    pub fn load_unchecked(ai: &AccountInfo) -> Result<Self, ProgramError> {
        msg!("ai:, {:?}, {:?}",ai,ai.data.borrow());
        Ok(Self::try_from_slice(&ai.data.borrow())?)
    }

    fn validate(&self) -> ProgramResult {
        if self.tag != AccountTag::Mint {
            return Err(ProgramError::InvalidAccountData);
        }
        Ok(())
    }

    pub fn load(ai: &AccountInfo) -> Result<Self, ProgramError> {
        let mint = Self::try_from_slice(&ai.data.borrow())?;
        mint.validate()?;
        Ok(mint)
    }

    pub fn save(&self, ai: &AccountInfo) -> ProgramResult {
        Ok(self.serialize(&mut *ai.data.borrow_mut())?)
    }
}

in the processor.rs

impl Processor {
    pub fn process_instruction(
        _program_id: &Pubkey,
        accounts: &[AccountInfo],
        instruction_data: &[u8],
    ) -> ProgramResult {
        let instruction = TokenInstruction::try_from_slice(instruction_data)
            .map_err(|_| ProgramError::InvalidInstructionData)?;
        let accounts_iter = &mut accounts.iter();
        match instruction {
            TokenInstruction::InitializeMint => {
                msg!("Instruction:  ,{:?}",instruction);
                let mint_ai = next_account_info(accounts_iter)?;
                msg!("mint_ai:  ,{:?}",mint_ai);
                let mint_authority = next_account_info(accounts_iter)?;
                msg!("mint_authority:  ,{:?},{:?}",mint_authority,*mint_authority.key);
                let mut mint = Mint::load_unchecked(mint_ai)?;
                msg!("mint:  ,{:?}",mint);
                assert_with_msg(
                    mint_authority.is_signer,
                    ProgramError::MissingRequiredSignature,
                    "Mint Authority must sign",
                )?;
                // TODO
                mint.tag = AccountTag::Mint;
                mint.authority = *mint_authority.key;
                mint.supply = 0;
                 
                msg!("NewMint:  ,{:?},{:?},{:?}",mint,mint_ai.data,&mut &mut mint_ai.data.borrow_mut()[..]);
                // mint.serialize(&mut &mut mint_ai.data.borrow_mut()[..])?;
                mint.save(mint_ai)?;  `

as the same parameters, the outputs are different: JS:

 <Buffer 01 2c 00 00 00 37 78 69 48 56 6e 37 45 65 51 77 62 73 50 72 41 74 61 59 33 61 6f 6b 48 65 6b 59 54 72 55 52 73 67 7a 52 65 46 78 64 31 4a 65 56 52 00 ... 7 more bytes>

RS:

 <Buffer 01 67 6b bf 1a 5c ad e2 3f a0 69 13 09 5e e8 6a 50 91 87 1c 3e 16 5d a4 43 84 d0 d6 f4 0e 9a 4f 8c 00 00 00 00 00 00 00 00>

Solution

  • On the frontend, sure to define the public key as 32 bytes, not as a string:

    const SCHEMA = new Map([
     [
       MintAccount,
       {
         kind: 'struct',
         fields: [
           ['tag', 'u8'],
           ['authority', [32]],
           ['supply', 'u64']
         ],
       },
     ]
    ]);
    

    You may need to update how the serialization is done, so instead of having a PublicKey on your type, it can be a Buffer or Uint8Array using authority.toBuffer() or authority.toBytes(), respectively.