新一代公链代表Solana(12) --- Solana的备忘录智能合约实战

实现一个备忘录合约程序。可以将一段内容存入到链上,并可以对其进行修改、删除。

1.项目结构:

在这里插入图片描述

2.entrypoint.rs

use {
    
    
    crate::error::HelloWorldError,
    crate::processor::Processor,
    num_traits::FromPrimitive,
    solana_program::{
    
    
        account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg,
        program_error::PrintProgramError, pubkey::Pubkey,
    },
};

entrypoint!(process_instruction);

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    
    
    msg!("Entrypoint");
    if let Err(error) = Processor::process_instruction(program_id, accounts, instruction_data) {
    
    
        // catch the error so we can print it
        error.print::<HelloWorldError>();
        return Err(error);
    }
    Ok(())
}

3.error.rs

use {
    
    
    num_derive::FromPrimitive,
    num_traits::FromPrimitive,
    solana_program::{
    
    
        decode_error::DecodeError, msg, program_error::PrintProgramError,
        program_error::ProgramError,
    },
    thiserror::Error,
};

#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum HelloWorldError {
    
    
    #[error("Not owned by HelloWolrd Program")]
    NotOwnedByHelloWrold,
}

pub type HelloWorldResult = Result<(), HelloWorldError>;

impl From<HelloWorldError> for ProgramError {
    
    
    fn from(e: HelloWorldError) -> Self {
    
    
        ProgramError::Custom(e as u32)
    }
}

impl<T> DecodeError<T> for HelloWorldError {
    
    
    fn type_of() -> &'static str {
    
    
        "HelloWorldError"
    }
}

impl PrintProgramError for HelloWorldError {
    
    
    fn print<E>(&self)
    where
        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
    {
    
    
        match self {
    
    
            HelloWorldError::NotOwnedByHelloWrold => {
    
    
                msg!("Error: Greeted account does not have the correct program id!")
            }
        }
    }
}

4.instruction.rs

use {
    
    
    borsh::{
    
    BorshDeserialize, BorshSchema, BorshSerialize},
    solana_program::{
    
    
        borsh::try_from_slice_unchecked,
        instruction::{
    
    AccountMeta, Instruction},
        program_error::ProgramError,
        pubkey::Pubkey,
    },
};

/// Instructions supported by the generic Name Registry program
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, BorshSchema)]
pub enum HelloWorldInstruction {
    
    
    Create(String),
    Modity(String),
    Delete,
}

4.lib.rs

pub mod entrypoint;
pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;

5.processor.rs

use {
    
    
    borsh::BorshDeserialize,
    borsh::BorshSerialize,
    crate::{
    
    error::HelloWorldError, instruction::HelloWorldInstruction, state::GreetingInfo},
    solana_program::{
    
    
        account_info::{
    
    next_account_info, AccountInfo},
        borsh::try_from_slice_unchecked,
        entrypoint::ProgramResult,
        msg,
        program_error::ProgramError,
        pubkey::Pubkey,
    },
};

pub struct Processor {
    
    }

impl Processor {
    
    
    pub fn process_create(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        msg: String,
    ) -> ProgramResult {
    
    
        let accounts_iter = &mut accounts.iter();

        let greeting_account = next_account_info(accounts_iter)?;

        // The account must be owned by the program in order to modify its data
        if greeting_account.owner != program_id {
    
    
            msg!("Greeted account does not have the correct program id");
            return Err(HelloWorldError::NotOwnedByHelloWrold.into());
        }

        // Increment and store the number of times the account has been greeted
        let mut greeting_info = GreetingInfo {
    
    
            message: "".to_string(),
        };
        greeting_info.message = msg;
        greeting_info.serialize(&mut *greeting_account.data.borrow_mut())?;

        msg!("create : set note to  {} !", greeting_info.message);
        Ok(())
    }

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

        let greeting_account = next_account_info(accounts_iter)?;

        // The account must be owned by the program in order to modify its data
        if greeting_account.owner != program_id {
    
    
            msg!("Greeted account does not have the correct program id");
            return Err(HelloWorldError::NotOwnedByHelloWrold.into());
        }

        // Increment and store the number of times the account has been greeted
        let mut greeting_info = GreetingInfo::try_from_slice(&greeting_account.data.borrow())?;
        greeting_info.message = msg;
        greeting_info.serialize(&mut *greeting_account.data.borrow_mut())?;

        msg!("modify : set note to  {} !", greeting_info.message);
        Ok(())
    }

    pub fn process_delete(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
    
    
        let accounts_iter = &mut accounts.iter();
        let user_account = next_account_info(accounts_iter)?;
        let greeting_account = next_account_info(accounts_iter)?;

        // The account must be owned by the program in order to modify its data
        if greeting_account.owner != program_id {
    
    
            msg!("Greeted account does not have the correct program id");
            return Err(HelloWorldError::NotOwnedByHelloWrold.into());
        }
        **user_account.try_borrow_mut_lamports()? += greeting_account.lamports();
        **greeting_account.try_borrow_mut_lamports()? = 0;

        msg!("delete : successful!");

        Ok(())
    }

    pub fn process_instruction(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        instruction_data: &[u8],
    ) -> ProgramResult {
    
    
        msg!("Beginning processing");
        let instruction = HelloWorldInstruction::try_from_slice(instruction_data)
            .map_err(|_| ProgramError::InvalidInstructionData)?;
        msg!("Instruction unpacked");

        match instruction {
    
    
            HelloWorldInstruction::Create(msg) => {
    
    
                Processor::process_create(program_id, accounts, msg)?;
            }
            HelloWorldInstruction::Modity(msg) => {
    
    
                Processor::process_modify(program_id, accounts, msg)?;
            }
            HelloWorldInstruction::Delete => {
    
    
                Processor::process_delete(program_id, accounts)?;
            }
        }
        Ok(())
    }
}

6.state.rs

use borsh::{
    
    BorshDeserialize, BorshSerialize};

/// Define the type of state stored in accounts
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GreetingInfo {
    
    
    /// number of greetings
    pub message: String,
}

7.Cargo.toml

[package]
name = "helloworld"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
solana-program = "1.16.4"
num-traits = "0.2"
borsh = "0.10"
num-derive = "0.3.3"
thiserror = "1.0.40"

8.client.ts

// No imports needed: web3, borsh, pg and more are globally available
import {
    
     serialize, deserialize, deserializeUnchecked } from "borsh";
import {
    
     Buffer } from "buffer";
/**
 * The state of a greeting account managed by the hello world program
 */

class Assignable {
    
    
  constructor(properties) {
    
    
    Object.keys(properties).map((key) => {
    
    
      return (this[key] = properties[key]);
    });
  }
}

class GreetingAccount {
    
    
  message = "1234567890";
  constructor(fields: {
    
     message: string } | undefined = undefined) {
    
    
    if (fields) {
    
    
      this.message = fields.message;
    }
  }
}
console.log("program", pg.PROGRAM_ID.toBase58());
/**
 * Borsh schema definition for greeting accounts
 */
const GreetingSchema = new Map([
  [GreetingAccount, {
    
     kind: "struct", fields: [["message", "string"]] }],
]);

// Our instruction payload vocabulary
class CreateInstruction extends Assignable {
    
    }
class ModifyInstruction extends Assignable {
    
    }
class DeleteInstruction extends Assignable {
    
    }

// Borsh needs a schema describing the payload
const helloWorldInstructionSchema = new Map([
  [
    CreateInstruction,
    {
    
    
      kind: "struct",
      fields: [
        ["id", "u8"],
        ["msg", "string"],
      ],
    },
  ],
  [
    ModifyInstruction,
    {
    
    
      kind: "struct",
      fields: [
        ["id", "u8"],
        ["msg", "string"],
      ],
    },
  ],
  [
    DeleteInstruction,
    {
    
    
      kind: "struct",
      fields: [["id", "u8"]],
    },
  ],
]);

// Instruction variant indexes
enum InstructionVariant {
    
    
  Create = 0,
  Modify = 1,
  Delete = 2,
}

/**
 * The expected size of each greeting account.
 */
const GREETING_SIZE = borsh.serialize(
  GreetingSchema,
  new GreetingAccount()
).length;

//Create greetings account instruction
const greetingAccountKp = new web3.Keypair();
const lamports = await pg.connection.getMinimumBalanceForRentExemption(
  GREETING_SIZE
);
const createGreetingAccountIx = web3.SystemProgram.createAccount({
    
    
  fromPubkey: pg.wallet.publicKey,
  lamports,
  newAccountPubkey: greetingAccountKp.publicKey,
  programId: pg.PROGRAM_ID,
  space: GREETING_SIZE,
});

const createIx = new CreateInstruction({
    
    
  id: InstructionVariant.Create,
  msg: "abc",
});

// Serialize the payload
const createSerBuf = Buffer.from(
  serialize(helloWorldInstructionSchema, createIx)
);
console.log("createSerBuf:", createSerBuf);
// Create greet instruction
const greetIx = new web3.TransactionInstruction({
    
    
  data: createSerBuf,
  keys: [
    {
    
    
      pubkey: greetingAccountKp.publicKey,
      isSigner: false,
      isWritable: true,
    },
  ],
  programId: pg.PROGRAM_ID,
});

// Create transaction and add the instructions
const tx = new web3.Transaction();
tx.add(createGreetingAccountIx, greetIx);
// tx.add(createGreetingAccountIx);
console.log("greetingAccountKp:", greetingAccountKp.publicKey.toBase58());
console.log("tx:", tx);
// Send and confirm the transaction
const txHash = await web3.sendAndConfirmTransaction(pg.connection, tx, [
  pg.wallet.keypair,
  greetingAccountKp,
]);
console.log(`Use 'solana confirm -v ${
    
    txHash}' to see the logs`);

// Fetch the greetings account
const greetingAccount = await pg.connection.getAccountInfo(
  greetingAccountKp.publicKey
);
console.log("data:", greetingAccount.data);

// Deserialize the account data
const deserializedAccountData = borsh.deserialize(
  GreetingSchema,
  GreetingAccount,
  greetingAccount.data.slice(0, 7)
);

console.log(
  `deserializedAccountData.counter ${
    
    deserializedAccountData.message}`
);

const modifyIx = new CreateInstruction({
    
    
  id: InstructionVariant.Create,
  msg: "def",
});

// Serialize the payload
const modifySerBuf = Buffer.from(
  serialize(helloWorldInstructionSchema, modifyIx)
);

// Create greet instruction
const modifyTI = new web3.TransactionInstruction({
    
    
  data: modifySerBuf,
  keys: [
    {
    
    
      pubkey: greetingAccountKp.publicKey,
      isSigner: false,
      isWritable: true,
    },
  ],
  programId: pg.PROGRAM_ID,
});

// Create transaction and add the instructions
const tx2 = new web3.Transaction();
tx2.add(modifyTI);

// Send and confirm the transaction
const txHash2 = await web3.sendAndConfirmTransaction(pg.connection, tx2, [
  pg.wallet.keypair,
  greetingAccountKp,
]);
console.log(`Use 'solana confirm -v ${
    
    txHash2}' to see the logs`);

// Fetch the greetings account
const greetingAccount2 = await pg.connection.getAccountInfo(
  greetingAccountKp.publicKey
);

// Deserialize the account data
const deserializedAccountData2 = borsh.deserialize(
  GreetingSchema,
  GreetingAccount,
  greetingAccount2.data.slice(0, 7)
);

console.log(
  `after modify deserializedAccountData.counter ${
    
    deserializedAccountData2.message}`
);

const deleteIx = new DeleteInstruction({
    
    
  id: InstructionVariant.Delete,
});

// Serialize the payload
const deleteSerBuf = Buffer.from(
  serialize(helloWorldInstructionSchema, deleteIx)
);

// Create greet instruction
const deleteTI = new web3.TransactionInstruction({
    
    
  data: deleteSerBuf,
  keys: [
    {
    
    
      pubkey: pg.wallet.keypair.publicKey,
      isSigner: true,
      isWritable: true,
    },
    {
    
    
      pubkey: greetingAccountKp.publicKey,
      isSigner: true,
      isWritable: true,
    },
  ],
  programId: pg.PROGRAM_ID,
});

// Create transaction and add the instructions
const tx3 = new web3.Transaction();
tx3.add(deleteTI);

// Send and confirm the transaction
const txHash3 = await web3.sendAndConfirmTransaction(pg.connection, tx3, [
  pg.wallet.keypair,
  greetingAccountKp,
]);
console.log(`Use 'solana confirm -v ${
    
    txHash3}' to see the logs`);

// Fetch the greetings account
const greetingAccount3 = await pg.connection.getAccountInfo(
  greetingAccountKp.publicKey
);

console.log(`greetingAccount3 ${
    
    greetingAccount3}`);

运行结果:

Running client...
  client.ts:
    program AVd3xt81AMcWjHLzsdJ7hnZYt6tCZjG3UmwHXWUKRRwm
    createSerBuf: <Buffer 00 03 00 00 00 61 62 63>
    greetingAccountKp: GLQABZQmT1tPgieNMWDSksdv9Hn9gnssXUNVaZhSwn4n
    tx: {
    
     signatures: [],
  feePayer: undefined,
  instructions: 
   [ {
    
     keys: [Object],
       programId: [Object],
       data: <Buffer 00 00 00 00 a0 14 0f 00 00 00 00 00 0e 00 00 00 00 00 00 00 8d 0e 35 45 9f 6b 0f ec dd e8 9c 76 96 74 15 98 11 6a a4 19 71 ff 87 88 ec 70 b0 26 fc c5 ... > },
     {
    
     keys: [Object],
       programId: [Object],
       data: <Buffer 00 03 00 00 00 61 62 63> } ],
  recentBlockhash: undefined,
  lastValidBlockHeight: undefined,
  nonceInfo: undefined,
  minNonceContextSlot: undefined,
  _message: undefined,
  _json: undefined }
    Use 'solana confirm -v 3uE1yH4CCMqdYncuEXELGy7RkFsrQsvxT7MdQTafKzmGqvibSbGyqEbWcq2cX2RRZZW4BgmTeMsLRxwoCJaa14Ad' to see the logs
    data: <Buffer 03 00 00 00 61 62 63 00 00 00 00 00 00 00>
    deserializedAccountData.counter abc
    Use 'solana confirm -v 3YFjjXf9Tu6madHF61YpqdxP2SbXKzRrymDvPhNNdLoef7kXhEymGktVJMDiURBULGnq8UHhy8tKoSim5cmfuwxu' to see the logs
    after modify deserializedAccountData.counter def
    Use 'solana confirm -v 5fBki64siNcqa68BLsyyMC9NazV7MbBrHvjZWUY2GA5e1mnyCTrak5sTYeMWEEX6NyDoVWX8DaPR3fC6qeA8yZ7Z' to see the logs
    greetingAccount3 null

区块链链接:
https://solscan.io/tx/3uE1yH4CCMqdYncuEXELGy7RkFsrQsvxT7MdQTafKzmGqvibSbGyqEbWcq2cX2RRZZW4BgmTeMsLRxwoCJaa14Ad?cluster=devnet
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qiangyipan1900/article/details/137523875