coral-xyz / anchor

⚓ Solana Sealevel Framework
https://anchor-lang.com
Apache License 2.0
3.57k stars 1.32k forks source link

Memo program not working properly in Anchor transactions #2193

Open AnderUstarroz opened 2 years ago

AnderUstarroz commented 2 years ago

Just realised that Memo program doesn't seem to work properly in Anchor transactions.

For instance a normal SOL transfer instruction + Memo:

const transaction = new Transaction();
transaction.add(
  SystemProgram.transfer({
    fromPubkey: my_keypair.publicKey,
    toPubkey: receiverPubkey,
    lamports: 50000,
  })
);
transaction.add({
  programId: MEMO_PROGRAM_ID,
  keys: [],
  data: Buffer.from("Hello world!", "utf8"),
});
let result = await provider.connection.sendTransaction(transaction, [my_keypair]);
await provider.connection.confirmTransaction(result, "confirmed");

console.log(await provider.connection.getSignaturesForAddress(receiverPubkey, { limit: 1000 }, "confirmed");

Will display the correct Memo "Hello world!" when calling getSignaturesForAddress():

[
  {
    blockTime: 1663653480,
    confirmationStatus: 'confirmed',
    err: null,
    memo: '[12] Hello world!',
    signature: '4sxYm9aex7sTB9HR4mJQuyEbAUnku2GMP8nfq8pepbszukTjseqFYVvpF8YrsobWC4JK4v3grhQievQymuaCWvb6',
    slot: 10
  },
  {
    blockTime: 1663653479,
    confirmationStatus: 'confirmed',
    err: null,
    memo: null,
    signature: '4frX4dHsnfRXGf8W9dz7MRqt6zTTaA5i3fuZJfXCF15CfGq4uxzpViUsULUojHN9y2f49mbxfuFLmocB2qxW4Z2h',
    slot: 8
  },

  ...
  ...
] 

But the corresponding anchor program transaction doesn't display the memo when calling getSignaturesForAddress():

use crate::state::Receiver;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::program::invoke;
use anchor_lang::system_program;
use spl_memo::build_memo;
use solana_program::pubkey::Pubkey;
use spl_memo::ID;

#[derive(Debug, Clone)]
pub struct Memo;

impl anchor_lang::Id for Memo {
    fn id() -> Pubkey {
        ID
    }
}

#[derive(Accounts)]
pub struct TransferFunds<'info> {
    pub payer: Signer<'info>,
    #[account(mut)]
    pub receiver: Account<'info, Receiver>,
    pub system_program: Program<'info, System>,
    pub memo_program: Program<'info, Memo>,
}

pub fn transfer_funds_with_memo(ctx: Context<TransferFunds>, lamports: u64) -> Result<()> {
    let transfer_cpi_context = CpiContext::new(
        ctx.accounts.system_program.to_account_info(),
        system_program::Transfer {
            from: ctx.accounts.payer.to_account_info(),
            to: ctx.accounts.receiver.to_account_info(),
        },
    );
    system_program::transfer(transfer_cpi_context, lamports)?;
    let memo_ix = build_memo("Hello world!".to_string().as_bytes(), &[]);
    invoke(&memo_ix, &[ctx.accounts.payer.to_account_info()])?;
    Ok(())
}

Testing the anchor program:

let tx = await program.methods
  .transferFundsWithMemo(100)
  .accounts({
    payer: provider.wallet.publicKey,
    receiver: receiverPubkey,
    memoProgram: new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
  })
  .signers([])
  .rpc({ commitment: "confirmed" });

let signatures = await provider.connection.getSignaturesForAddress(
  receiverPubkey,
  params,
  "confirmed"
);
console.log("SIGNATURES: ", signatures);

Here memo is not displayed:

SIGNATURES:  [
  {
    blockTime: 1663653479,
    confirmationStatus: 'confirmed',
    err: null,
    memo: null,    <------ NOTHING HERE!
    signature: '4frX4dHsnfRXGf8W9dz7MRqt6zTTaA5i3fuZJfXCF15CfGq4uxzpViUsULUojHN9y2f49mbxfuFLmocB2qxW4Z2h',
    slot: 8
  },

   ...
   ...
]

Which is strange specially considering that the memo was successfully added and can be seen by fetching the transaction:

const tx = await provider.connection.getTransaction("4frX4dHsnfRXGf8W9dz7MRqt6zTTaA5i3fuZJfXCF15CfGq4uxzpViUsULUojHN9y2f49mbxfuFLmocB2qxW4Z2h", {commitment: "confirmed"});
console.log(tx);

Logs:

{
  blockTime: 1663653479,
  meta: {
    err: null,
    fee: 5000,
    innerInstructions: [ [Object] ],
    loadedAddresses: { readonly: [], writable: [] },
    logMessages: [
      'Program kul6QsynjfrPda7aWQjyYi4wDm6btb84fu1PwuqewiF invoke [1]',
      'Program log: Instruction: PlaceSolBet',
      'Program 11111111111111111111111111111111 invoke [2]',
      'Program 11111111111111111111111111111111 success',
      'Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr invoke [2]',
      'Program log: Memo (len 12): "Hello world!"',    <------ Here Memo is displayed!
      'Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr consumed 3953 of 192080 compute units',
      'Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr success',
      'Program kul6QsynjfrPda7aWQjyYi4wDm6btb84fu1PwuqewiF consumed 15111 of 200000 compute units',
      'Program kul6QsynjfrPda7aWQjyYi4wDm6btb84fu1PwuqewiF success'
    ],
    postBalances: [ 499999999975981900, 7850890, 1, 1, 1 ],
    postTokenBalances: [],
    preBalances: [ 499999999975986940, 7850880, 1, 1, 1 ],
    preTokenBalances: [],
    rewards: [],
    status: { Ok: null }
  },
  slot: 8,
  transaction: {
    message: Message {
      header: [Object],
      accountKeys: [Array],
      recentBlockhash: 'GFqrTKjo9ZAfLT6u4o7ydBU61AXSpGJpEKjvWYLgxBKX',
      instructions: [Array],
      indexToProgramIds: [Map]
    },
    signatures: [
      '4frX4dHsnfRXGf8W9dz7MRqt6zTTaA5i3fuZJfXCF15CfGq4uxzpViUsULUojHN9y2f49mbxfuFLmocB2qxW4Z2h'
    ]
  }
}
peterschwarzdev commented 2 years ago

Great research! Being able to use getSignaturesForAddress() to get all transactions + memos at once is a huge performance boost vs having to query every single transaction individually. Hopefully they will fix this issue.

Henry-E commented 1 year ago

A lot of text here. Is the issue just that provider.connection.getSignaturesForAddress somehow drops the memo information passed back by the RPC call?

Happy to review a PR that fixes. It's also worth noting that connection.getSignaturesForAddress is a web3 library function, so the issue could lay there. 🤷