Migrating a Programs Data Accounts
How can you migrate a program's data accounts?
당신이 Program을 생성할 때, 그 Program과 연관된 각각의 Data Account는 특수한 Data 구조를 가질 것입니다. 만약 당신이 Program Derived Account를 업그레이드 할 필요가 있다면, 당신은 결국 이전 구주를 갖고 남아 있는 다수의 Program Derived Account를 갖게 될 것입니다.
Account versioning을 통해 당신은 이전 Account들을 새로운 구조로 업그레이드 할 수 있습니다.
Note
이것은 단지 Program Owned Account(POA)들에 있는 데이터를 이관하는 많은 방법들 중 하나일 뿐입니다.
Scenario
우리의 Account Data에 version을 명시하고 이관하기 위해서, 우리는 각 Account에 대한 하나의 id를 받을 것입니다. 이 id는 Account의 version을 식별할 수 있게 해줍니다. 우리가 id를 Program에 보냄으로써 Account를 정확히 다룰 수 있습니다.
아래는 Account 상태와 Program을 나타냅니다:
//! @brief account_state manages account data
use arrayref::{array_ref, array_refs};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
msg,
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
};
use std::{io::BufWriter, mem};
/// Currently using state. If version changes occur, this
/// should be copied to another serializable backlevel one
/// before adding new fields here
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentCurrent {
pub somevalue: u64,
}
/// Maintains account data
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct ProgramAccountState {
is_initialized: bool,
data_version: u8,
account_data: AccountContentCurrent,
}
impl ProgramAccountState {
/// Signal initialized
pub fn set_initialized(&mut self) {
self.is_initialized = true;
}
/// Get the initialized flag
pub fn initialized(&self) -> bool {
self.is_initialized
}
/// Gets the current data version
pub fn version(&self) -> u8 {
self.data_version
}
/// Get the reference to content structure
pub fn content(&self) -> &AccountContentCurrent {
&self.account_data
}
/// Get the mutable reference to content structure
pub fn content_mut(&mut self) -> &mut AccountContentCurrent {
&mut self.account_data
}
}
/// Declaration of the current data version.
pub const DATA_VERSION: u8 = 0;
/// Account allocated size
pub const ACCOUNT_ALLOCATION_SIZE: usize = 1024;
/// Initialized flag is 1st byte of data block
const IS_INITIALIZED: usize = 1;
/// Data version (current) is 2nd byte of data block
const DATA_VERSION_ID: usize = 1;
/// Previous content data size (before changing this is equal to current)
pub const PREVIOUS_VERSION_DATA_SIZE: usize = mem::size_of::<AccountContentCurrent>();
/// Total space occupied by previous account data
pub const PREVIOUS_ACCOUNT_SPACE: usize =
IS_INITIALIZED + DATA_VERSION_ID + PREVIOUS_VERSION_DATA_SIZE;
/// Current content data size
pub const CURRENT_VERSION_DATA_SIZE: usize = mem::size_of::<AccountContentCurrent>();
/// Total usage for data only
pub const CURRENT_USED_SIZE: usize = IS_INITIALIZED + DATA_VERSION_ID + CURRENT_VERSION_DATA_SIZE;
/// How much of 1024 is used
pub const CURRENT_UNUSED_SIZE: usize = ACCOUNT_ALLOCATION_SIZE - CURRENT_USED_SIZE;
/// Current space used by header (initialized, data version and Content)
pub const ACCOUNT_STATE_SPACE: usize = CURRENT_USED_SIZE + CURRENT_UNUSED_SIZE;
/// Future data migration logic that converts prior state of data
/// to current state of data
fn conversion_logic(src: &[u8]) -> Result<ProgramAccountState, ProgramError> {
let past = array_ref![src, 0, PREVIOUS_ACCOUNT_SPACE];
let (initialized, _, _account_space) = array_refs![
past,
IS_INITIALIZED,
DATA_VERSION_ID,
PREVIOUS_VERSION_DATA_SIZE
];
// Logic to uplift from previous version
// GOES HERE
// Give back
Ok(ProgramAccountState {
is_initialized: initialized[0] != 0u8,
data_version: DATA_VERSION,
account_data: AccountContentCurrent::default(),
})
}
impl Sealed for ProgramAccountState {}
impl IsInitialized for ProgramAccountState {
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
impl Pack for ProgramAccountState {
const LEN: usize = ACCOUNT_STATE_SPACE;
/// Store 'state' of account to its data area
fn pack_into_slice(&self, dst: &mut [u8]) {
let mut bw = BufWriter::new(dst);
self.serialize(&mut bw).unwrap();
}
/// Retrieve 'state' of account from account data area
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let initialized = src[0] != 0;
// Check initialized
if initialized {
// Version check
if src[1] == DATA_VERSION {
msg!("Processing consistent data");
Ok(
ProgramAccountState::try_from_slice(array_ref![src, 0, CURRENT_USED_SIZE])
.unwrap(),
)
} else {
msg!("Processing backlevel data");
conversion_logic(src)
}
} else {
msg!("Processing pre-initialized data");
Ok(ProgramAccountState {
is_initialized: false,
data_version: DATA_VERSION,
account_data: AccountContentCurrent::default(),
})
}
}
}
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentCurrent {
pub somevalue: u64,
}
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct ProgramAccountState {
is_initialized: bool,
data_version: u8,
account_data: AccountContentCurrent,
}
//! instruction Contains the main ProgramInstruction enum
use {
crate::error::DataVersionError,
borsh::{BorshDeserialize, BorshSerialize},
solana_program::program_error::ProgramError,
};
#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq)]
/// All custom program instructions
pub enum ProgramInstruction {
InitializeAccount,
SetU64Value(u64),
FailInstruction,
}
impl ProgramInstruction {
/// Unpack inbound buffer to associated Instruction
/// The expected format for input is a Borsh serialized vector
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
let payload = ProgramInstruction::try_from_slice(input).unwrap();
match payload {
ProgramInstruction::InitializeAccount => Ok(payload),
ProgramInstruction::SetU64Value(_) => Ok(payload),
_ => Err(DataVersionError::InvalidInstruction.into()),
}
}
}
impl ProgramInstruction {
/// Unpack inbound buffer to associated Instruction
/// The expected format for input is a Borsh serialized vector
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
let payload = ProgramInstruction::try_from_slice(input).unwrap();
match payload {
ProgramInstruction::InitializeAccount => Ok(payload),
ProgramInstruction::SetU64Value(_) => Ok(payload),
_ => Err(DataVersionError::InvalidInstruction.into()),
}
}
}
//! Resolve instruction and execute
use crate::{
account_state::ProgramAccountState, error::DataVersionError, instruction::ProgramInstruction,
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
};
/// Checks each tracking account to confirm it is owned by our program
/// This function assumes that the program account is always the last
/// in the array
fn check_account_ownership(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
// Accounts must be owned by the program.
for account in accounts.iter().take(accounts.len() - 1) {
if account.owner != program_id {
msg!(
"Fail: The tracking account owner is {} and it should be {}.",
account.owner,
program_id
);
return Err(ProgramError::IncorrectProgramId);
}
}
Ok(())
}
/// Initialize the programs account, which is the first in accounts
fn initialize_account(accounts: &[AccountInfo]) -> ProgramResult {
msg!("Initialize account");
let account_info_iter = &mut accounts.iter();
let program_account = next_account_info(account_info_iter)?;
let mut account_data = program_account.data.borrow_mut();
// Just using unpack will check to see if initialized and will
// fail if not
let mut account_state = ProgramAccountState::unpack_unchecked(&account_data)?;
// Where this is a logic error in trying to initialize the same account more than once
if account_state.is_initialized() {
return Err(DataVersionError::AlreadyInitializedState.into());
} else {
account_state.set_initialized();
account_state.content_mut().somevalue = 1;
}
msg!("Account Initialized");
// Serialize
ProgramAccountState::pack(account_state, &mut account_data)
}
/// Sets the u64 in the content structure
fn set_u64_value(accounts: &[AccountInfo], value: u64) -> ProgramResult {
msg!("Set new value {}", value);
let account_info_iter = &mut accounts.iter();
let program_account = next_account_info(account_info_iter)?;
let mut account_data = program_account.data.borrow_mut();
let mut account_state = ProgramAccountState::unpack(&account_data)?;
account_state.content_mut().somevalue = value;
// Serialize
ProgramAccountState::pack(account_state, &mut account_data)
}
/// Main processing entry point dispatches to specific
/// instruction handlers
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("Received process request");
// Check the account for program relationship
if let Err(error) = check_account_ownership(program_id, accounts) {
return Err(error);
};
// Unpack the inbound data, mapping instruction to appropriate structure
let instruction = ProgramInstruction::unpack(instruction_data)?;
match instruction {
ProgramInstruction::InitializeAccount => initialize_account(accounts),
ProgramInstruction::SetU64Value(value) => set_u64_value(accounts, value),
_ => {
msg!("Received unknown instruction");
Err(DataVersionError::InvalidInstruction.into())
}
}
}
fn check_account_ownership(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
// Accounts must be owned by the program.
for account in accounts.iter().take(accounts.len() - 1) {
if account.owner != program_id {
msg!(
"Fail: The tracking account owner is {} and it should be {}.",
account.owner,
program_id
);
return Err(ProgramError::IncorrectProgramId);
}
}
Ok(())
}
/// Initialize the programs account, which is the first in accounts
fn initialize_account(accounts: &[AccountInfo]) -> ProgramResult {
msg!("Initialize account");
let account_info_iter = &mut accounts.iter();
let program_account = next_account_info(account_info_iter)?;
let mut account_data = program_account.data.borrow_mut();
// Just using unpack will check to see if initialized and will
// fail if not
let mut account_state = ProgramAccountState::unpack_unchecked(&account_data)?;
// Where this is a logic error in trying to initialize the same account more than once
if account_state.is_initialized() {
return Err(DataVersionError::AlreadyInitializedState.into());
} else {
account_state.set_initialized();
account_state.content_mut().somevalue = 1;
}
msg!("Account Initialized");
// Serialize
ProgramAccountState::pack(account_state, &mut account_data)
}
/// Sets the u64 in the content structure
fn set_u64_value(accounts: &[AccountInfo], value: u64) -> ProgramResult {
msg!("Set new value {}", value);
let account_info_iter = &mut accounts.iter();
let program_account = next_account_info(account_info_iter)?;
let mut account_data = program_account.data.borrow_mut();
let mut account_state = ProgramAccountState::unpack(&account_data)?;
account_state.content_mut().somevalue = value;
// Serialize
ProgramAccountState::pack(account_state, &mut account_data)
}
/// Main processing entry point dispatches to specific
/// instruction handlers
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("Received process request");
// Check the account for program relationship
if let Err(error) = check_account_ownership(program_id, accounts) {
return Err(error);
};
// Unpack the inbound data, mapping instruction to appropriate structure
let instruction = ProgramInstruction::unpack(instruction_data)?;
match instruction {
ProgramInstruction::InitializeAccount => initialize_account(accounts),
ProgramInstruction::SetU64Value(value) => set_u64_value(accounts, value),
_ => {
msg!("Received unknown instruction");
Err(DataVersionError::InvalidInstruction.into())
}
}
}
우리의 첫 번째 Account 버전에서 우리는 아래의 것들을 하고 있습니다:
ID | Action |
---|---|
1 | 너의 data 안에 있는 'data version' 필드를 포함. 이것은 단순히 순서를 증가시키는 것이거나 좀 더 복잡한 무언가가 될 수 있습니다. |
2 | Data 성장을 위한 충분한 공간 할당 |
3 | Program Version들 사이에서 사용될 수 있는 몇 가지 상수들에 대한 초기화. |
4 | 미래에 업그레이드를 위한 fn conversion_logic 에 Account 업데이트 기능 추가 |
이제 somestring
이라는 새롭게 요구되는 필드를 포함하기 위해 우리 Program의 Account들을 업그레이드해봅시다.
만약 우리가 이전 Account에 여분의 공간을 할당하지 않았다면, 우리는 Account를 업그레이드 할 수 없을 것입니다.
Upgrading the Account
우리의 새로운 Program에서 우리는 content state를 위해 새로운 속성을 추가하고 싶습니다. 아래의 변경 사항은 우리가 초기 Program의 구조들을 어떻게 변경하는지에 대한 것입니다.
1. Add account conversion logic
//! @brief account_state manages account data
use arrayref::{array_ref, array_refs};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
borsh::try_from_slice_unchecked,
msg,
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
};
use std::{io::BufWriter, mem};
/// Current state (DATA_VERSION 1). If version changes occur, this
/// should be copied to another (see AccountContentOld below)
/// We've added a new field: 'somestring'
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentCurrent {
pub somevalue: u64,
pub somestring: String,
}
/// Old content state (DATA_VERSION 0).
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentOld {
pub somevalue: u64,
}
/// Maintains account data
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct ProgramAccountState {
is_initialized: bool,
data_version: u8,
account_data: AccountContentCurrent,
}
impl ProgramAccountState {
/// Signal initialized
pub fn set_initialized(&mut self) {
self.is_initialized = true;
}
/// Get the initialized flag
pub fn initialized(&self) -> bool {
self.is_initialized
}
/// Gets the current data version
pub fn version(&self) -> u8 {
self.data_version
}
/// Get the reference to content structure
pub fn content(&self) -> &AccountContentCurrent {
&self.account_data
}
/// Get the mutable reference to content structure
pub fn content_mut(&mut self) -> &mut AccountContentCurrent {
&mut self.account_data
}
}
/// Declaration of the current data version.
const DATA_VERSION: u8 = 1; // Adding string to content
// Previous const DATA_VERSION: u8 = 0;
/// Account allocated size
const ACCOUNT_ALLOCATION_SIZE: usize = 1024;
/// Initialized flag is 1st byte of data block
const IS_INITIALIZED: usize = 1;
/// Data version (current) is 2nd byte of data block
const DATA_VERSION_ID: usize = 1;
/// Previous content data size (before changing this is equal to current)
const PREVIOUS_VERSION_DATA_SIZE: usize = mem::size_of::<AccountContentOld>();
/// Total space occupied by previous account data
const PREVIOUS_ACCOUNT_SPACE: usize = IS_INITIALIZED + DATA_VERSION_ID + PREVIOUS_VERSION_DATA_SIZE;
/// Current content data size
const CURRENT_VERSION_DATA_SIZE: usize = mem::size_of::<AccountContentCurrent>();
/// Total usage for data only
const CURRENT_USED_SIZE: usize = IS_INITIALIZED + DATA_VERSION_ID + CURRENT_VERSION_DATA_SIZE;
/// How much of 1024 is used
const CURRENT_UNUSED_SIZE: usize = ACCOUNT_ALLOCATION_SIZE - CURRENT_USED_SIZE;
/// Current space used by header (initialized, data version and Content)
pub const ACCOUNT_STATE_SPACE: usize = CURRENT_USED_SIZE + CURRENT_UNUSED_SIZE;
/// Future data migration logic that converts prior state of data
/// to current state of data
fn conversion_logic(src: &[u8]) -> Result<ProgramAccountState, ProgramError> {
let past = array_ref![src, 0, PREVIOUS_ACCOUNT_SPACE];
let (initialized, _, account_space) = array_refs![
past,
IS_INITIALIZED,
DATA_VERSION_ID,
PREVIOUS_VERSION_DATA_SIZE
];
// Logic to upgrade from previous version
// GOES HERE
let old = try_from_slice_unchecked::<AccountContentOld>(account_space).unwrap();
// Default sets 'somevalue' to 0 and somestring to default ""
let mut new_content = AccountContentCurrent::default();
// We copy the existing 'somevalue', the program instructions will read/update 'somestring' without fail
new_content.somevalue = old.somevalue;
// Give back
Ok(ProgramAccountState {
is_initialized: initialized[0] != 0u8,
data_version: DATA_VERSION,
account_data: new_content,
})
}
impl Sealed for ProgramAccountState {}
impl IsInitialized for ProgramAccountState {
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
impl Pack for ProgramAccountState {
const LEN: usize = ACCOUNT_STATE_SPACE;
/// Store 'state' of account to its data area
fn pack_into_slice(&self, dst: &mut [u8]) {
let mut bw = BufWriter::new(dst);
self.serialize(&mut bw).unwrap();
}
/// Retrieve 'state' of account from account data area
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let initialized = src[0] != 0;
// Check initialized
if initialized {
// Version check
if src[1] == DATA_VERSION {
msg!("Processing consistent version data");
Ok(try_from_slice_unchecked::<ProgramAccountState>(src).unwrap())
} else {
msg!("Processing backlevel data");
conversion_logic(src)
}
} else {
msg!("Processing pre-initialized data");
Ok(ProgramAccountState {
is_initialized: false,
data_version: DATA_VERSION,
account_data: AccountContentCurrent::default(),
})
}
}
}
/// Current state (DATA_VERSION 1). If version changes occur, this
/// should be copied to another (see AccountContentOld below)
/// We've added a new field: 'somestring'
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentCurrent {
pub somevalue: u64,
pub somestring: String,
}
/// Old content state (DATA_VERSION 0).
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentOld {
pub somevalue: u64,
}
/// Maintains account data
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct ProgramAccountState {
is_initialized: bool,
data_version: u8,
account_data: AccountContentCurrent,
}
Line(s) | Note |
---|---|
6 | 더 큰 Data block으로부터 간단히 데이터의 일부를 읽기 위해 Solana의 solana_program::borsh::try_from_slice_unchecked 를 추가했습니다. |
13-26 | line 17에서 시작하는 AccountContentCurrent 가 확장하기 전에, AccountContentOld line 24에서 이전 Content 구조를 보존합니다. |
60 | DATA_VERSION 상수 |
71 | 우리는 이제 'previous' version을 갖고 있고 이것의 사이즈를 알고 싶습니다. |
86 | 이전 content 상태를 새로운(현재) content 상태로 업그레이드 하기 위한 연결 고리에 대한 추가. |
그러고 나서 새로운 Instruction들을 다루기 위한 프로세서와 somestring
을 업데이트 하기 위해 우리의 Instruction들을 업데이트 합니다. 데이터 구조에 대한 'upgrading'은 pack/unpack
뒤로 캡슐화 되어 있다는 사실에 주의하세요.
//! instruction Contains the main VersionProgramInstruction enum
use {
crate::error::DataVersionError,
borsh::{BorshDeserialize, BorshSerialize},
solana_program::{borsh::try_from_slice_unchecked, msg, program_error::ProgramError},
};
#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq)]
/// All custom program instructions
pub enum VersionProgramInstruction {
InitializeAccount,
SetU64Value(u64),
SetString(String), // Added with data version change
FailInstruction,
}
impl VersionProgramInstruction {
/// Unpack inbound buffer to associated Instruction
/// The expected format for input is a Borsh serialized vector
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
let payload = try_from_slice_unchecked::<VersionProgramInstruction>(input).unwrap();
// let payload = VersionProgramInstruction::try_from_slice(input).unwrap();
match payload {
VersionProgramInstruction::InitializeAccount => Ok(payload),
VersionProgramInstruction::SetU64Value(_) => Ok(payload),
VersionProgramInstruction::SetString(_) => Ok(payload), // Added with data version change
_ => Err(DataVersionError::InvalidInstruction.into()),
}
}
}
//! Resolve instruction and execute
use crate::{
account_state::ProgramAccountState, error::DataVersionError,
instruction::VersionProgramInstruction,
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
};
/// Checks each tracking account to confirm it is owned by our program
/// This function assumes that the program account is always the last
/// in the array
fn check_account_ownership(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
// Accounts must be owned by the program.
for account in accounts.iter().take(accounts.len() - 1) {
if account.owner != program_id {
msg!(
"Fail: The tracking account owner is {} and it should be {}.",
account.owner,
program_id
);
return Err(ProgramError::IncorrectProgramId);
}
}
Ok(())
}
/// Initialize the programs account, which is the first in accounts
fn initialize_account(accounts: &[AccountInfo]) -> ProgramResult {
msg!("Initialize account");
let account_info_iter = &mut accounts.iter();
let program_account = next_account_info(account_info_iter)?;
let mut account_data = program_account.data.borrow_mut();
// Just using unpack will check to see if initialized and will
// fail if not
let mut account_state = ProgramAccountState::unpack_unchecked(&account_data)?;
// Where this is a logic error in trying to initialize the same account more than once
if account_state.is_initialized() {
return Err(DataVersionError::AlreadyInitializedState.into());
} else {
account_state.set_initialized();
account_state.content_mut().somevalue = 1;
}
msg!("Account Initialized");
// Serialize
ProgramAccountState::pack(account_state, &mut account_data)
}
/// Sets the u64 in the content structure
fn set_u64_value(accounts: &[AccountInfo], value: u64) -> ProgramResult {
msg!("Set new value {}", value);
let account_info_iter = &mut accounts.iter();
let program_account = next_account_info(account_info_iter)?;
let mut account_data = program_account.data.borrow_mut();
let mut account_state = ProgramAccountState::unpack(&account_data)?;
account_state.content_mut().somevalue = value;
// Serialize
ProgramAccountState::pack(account_state, &mut account_data)
}
/// Sets the string in the content structure
fn set_string_value(accounts: &[AccountInfo], value: String) -> ProgramResult {
msg!("Set new string {}", value);
let account_info_iter = &mut accounts.iter();
let program_account = next_account_info(account_info_iter)?;
let mut account_data = program_account.data.borrow_mut();
let mut account_state = ProgramAccountState::unpack(&account_data)?;
account_state.content_mut().somestring = value;
// Serialize
ProgramAccountState::pack(account_state, &mut account_data)
}
/// Main processing entry point dispatches to specific
/// instruction handlers
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("Received process request 0.2.0");
// Check the account for program relationship
if let Err(error) = check_account_ownership(program_id, accounts) {
return Err(error);
};
// Unpack the inbound data, mapping instruction to appropriate structure
msg!("Attempting to unpack");
let instruction = VersionProgramInstruction::unpack(instruction_data)?;
match instruction {
VersionProgramInstruction::InitializeAccount => initialize_account(accounts),
VersionProgramInstruction::SetU64Value(value) => set_u64_value(accounts, value),
VersionProgramInstruction::SetString(value) => set_string_value(accounts, value),
_ => {
msg!("Received unknown instruction");
Err(DataVersionError::InvalidInstruction.into())
}
}
}
VersionProgramInstruction::SetString(String)
이라는 Instruction을 빌드하고 제출한 후, 이제 우리는 아래의 '업그레이드된' Account Data 구조를 갖습니다.