freeCodeCamp / solana-curriculum

freeCodeCamp Solana Curriculum
BSD 3-Clause "New" or "Revised" License
123 stars 50 forks source link

[HELP]: Build-A-Smart-Contract Test #12 Not Passing #342

Closed ChiefWoods closed 4 months ago

ChiefWoods commented 4 months ago

Project

Build-A-Smart-Contract

Lesson Number

Question

I've tried many modifications and just can't get test #12 to pass, even if the message is successfully stored on the program's data.

Code and Screenshots

lib.rs

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg,
    program_error::ProgramError, pubkey::Pubkey,
};

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let mut accounts_iter = accounts.iter();

    if let Some(account) = accounts_iter.next() {
        if account.owner != program_id {
            msg!("Account info does not match program id");
            return Err(ProgramError::IncorrectProgramId);
        };

        let mut message_account =
            MessageAccount::try_from_slice(instruction_data).map_err(|_| {
                msg!("Message is not valid");
                ProgramError::InvalidInstructionData
            })?;

        if message_account.message.len() > 280 {
            msg!("Message exceeded max length of 280 characters");
            return Err(ProgramError::InvalidInstructionData);
        };

        message_account.message = format!("{: <280}", message_account.message);

        let acc_data = &mut account.data.borrow_mut()[..];
        message_account.serialize(&mut acc_data.as_mut())?;

        msg!("Message: {}", message_account.message);

        Ok(())
    } else {
        msg!("No accounts provided");
        return Err(ProgramError::NotEnoughAccountKeys);
    }
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct MessageAccount {
    pub message: String,
}

entrypoint!(process_instruction);

main.js

import {
  establishConnection,
  getProgramId,
  establishPayer,
  getAccountPubkey,
  checkProgram,
  changeMessage,
  getMessage
} from "./message.js";

async function main() {
  const args = process.argv.slice(2);

  if (!args.length) {
    throw Error("No message provided");
  }

  const connection = establishConnection();
  console.log("Changing message");
  const programId = await getProgramId();
  const payer = await establishPayer();
  const accountPubkey = await getAccountPubkey(payer, programId);
  await checkProgram(connection, payer, programId, accountPubkey);
  await changeMessage(connection, payer, programId, accountPubkey, args[0]);
  const message = await getMessage(connection, accountPubkey);
  console.log('New message:', message.trim());
}

await main();

message.js

import { Connection, Keypair, PublicKey, Transaction, SystemProgram, sendAndConfirmTransaction, TransactionInstruction } from "@solana/web3.js";
import { readFile } from 'fs/promises';
import * as borsh from 'borsh';

class MessageAccount {
  constructor (fields) {
    if (fields !== undefined) {
      this.message = fields.message;
    }
  }
}

const MessageSchema = new Map([
  [MessageAccount, { kind: 'struct', fields: [['message', 'String']] }]
]);

const fixedAccount = new MessageAccount({ message: '' });
while (fixedAccount.message.length < 280) {
  fixedAccount.message += ' ';
}
const ACCOUNT_SIZE = borsh.serialize(MessageSchema, fixedAccount).length;

export function establishConnection() {
  return new Connection('http://localhost:8899');
}

export async function establishPayer() {
  const secretKeyString = await readFile('wallet.json', 'utf8');
  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));
  return Keypair.fromSecretKey(secretKey);
}

export async function getProgramId() {
  const secretKeyString = await readFile('dist/program/message-keypair.json', 'utf8');
  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));
  const keypair = Keypair.fromSecretKey(secretKey);
  return keypair.publicKey;
}

export async function getAccountPubkey(payer, programId) {
  return await PublicKey.createWithSeed(payer.publicKey, 'fcc-seed', programId);
}

export async function checkProgram(connection, payer, programId, accountPubkey) {
  const programAccount = await connection.getAccountInfo(programId);

  if (!programAccount) {
    throw Error('Account not found');
  } else if (!programAccount.executable) {
    throw Error('Account not executable');
  }

  const programDataAccount = await connection.getAccountInfo(accountPubkey);

  if (!programDataAccount) {
    await createAccount(connection, payer, programId, accountPubkey);
  }
};

export async function createAccount(connection, payer, programId, accountPubkey) {
  const lamports = await connection.getMinimumBalanceForRentExemption(ACCOUNT_SIZE);
  const transaction = new Transaction(payer);
  const instruction = {
    basePubkey: payer.publicKey,
    fromPubkey: payer.publicKey,
    lamports,
    newAccountPubkey: accountPubkey,
    programId,
    seed: 'fcc-seed',
    space: ACCOUNT_SIZE,
  };
  const tx = SystemProgram.createAccountWithSeed(instruction);
  transaction.add(tx);
  await sendAndConfirmTransaction(connection, transaction, [payer]);
}

export async function changeMessage(connection, payer, programId, accountPubkey, message) {
  const messageAccount = new MessageAccount({ message });

  const transaction = {
    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],
    programId,
    data: Buffer.from(borsh.serialize(MessageSchema, messageAccount)),
  };
  const instruction = new TransactionInstruction(transaction);
  await sendAndConfirmTransaction(connection, new Transaction().add(instruction), [payer]);
}

export async function getMessage(connection, accountPubkey) {
  const accountInfo = await connection.getAccountInfo(accountPubkey);
  const message = borsh.deserialize(MessageSchema, MessageAccount, accountInfo.data);
  return message.message;
}
ShaunSHamilton commented 4 months ago

This is the code/test run for number 12: https://github.com/freeCodeCamp/solana-curriculum/blob/7485d98f6a46f44b0f684e1c1d3facbd730c3df8/build-a-smart-contract/program/tests/process_instruction.rs#L35-L55

I think we resolved this on Discord?

The issue is:

        let mut message_account =
            MessageAccount::try_from_slice(instruction_data).map_err(|_| {
                msg!("Message is not valid");
                ProgramError::InvalidInstructionData
            })?;

because instruction_data is not a serialized MessageAccount - therefore, it cannot be deserialized into one.