CosmWasm Smart Contracts to Solana Programs
Learn the difference between CosmWasm and Solana smart contracts.
CosmWasm
- Execution Environment: Runs on the Cosmos SDK, designed for interoperability between blockchains.
- State Management: Uses
cosmwasm_storage
for storing and retrieving state. - Entry Points: Separate entry points for instantiate, execute, and query.
- Messages: Uses
InstantiateMsg
,ExecuteMsg
, andQueryMsg
to define contract interactions.
Solana
- Execution Environment: Runs on the Solana blockchain, optimized for high throughput and low latency.
- State Management: Primarily uses
Borsh
for serialization and deserialization of state stored in accounts. Solana recommends Borsh for its efficiency, but developers can use other serialization formats like bincode if desired. - Entry Point: A single entry point (process_instruction) handles all instructions.
- Instructions: Uses custom-defined instructions encoded typically as byte arrays, which are parsed within process_instruction to determine the specific operations to perform. Instructions must be explicitly defined and handled within the smart contract.
State Management
In CosmWasm, state is stored in the smart contract's storage, which is managed
by the Cosmos SDK. This contract interacts with this storage via helper
functions provided by cosmwasm-storage
. This storage is part of the blockchain
state and is specific to each contract instance. State variables are typically
defined in a state.rs
file and are serialized/deserialized using serde
.
State management functions are used within the contract logic to read from and
write to the storage.
In contrast, Solana programs manage state by interacting with the data of specific accounts that are passed into the program during execution. These accounts can be created and assigned by the System Program and are allocated a certain amount of space for storing data. The state within these accounts is serialized and deserialized using libraries like Borsh. Solana programs are responsible for interpreting the account data as state information, writing to and reading from these accounts directly in the program logic.
The core concept of accounts on Solana is crucial to understanding state management and effectively designing Solana programs. Each account on Solana can be associated with a specific program (smart contract), and only the owning program can modify its state. This model differs significantly from many other blockchain platforms where the state is managed more directly through the smart contract itself. For detailed information on the Solana Account Model, refer to the Solana documentation here.
Key Differences
- Storage Abstraction:
- CosmWasm: Uses abstractions like
cosmwasm_storage
to manage state. The contract interacts with a key-value storage system provided by the Cosmos SDK. - Solana: Directly reads from and writes to account data, which is part of the account's state on the Solana blockchain.
- CosmWasm: Uses abstractions like
- Serialization:
- CosmWasm: Uses a mix of JSON for interoperability and human readability, and binary formats like bincode for state storage.
- Solana: Typically uses Borsh for all serialization purposes to ensure speed and consistency in a high throughput environment, but have the option to roll your own if you choose to.
- State Location:
- CosmWasm: State is stored in the contract's storage, managed by the Cosmos SDK.
- Solana: State is stored in account data, which is passed to the program during execution.
- Access Patterns:
- CosmWasm: State is accessed via storage APIs provided by CosmWasm.
- Solana: State is accessed by directly manipulating the data of accounts.
Converting CosmWasm State Management to Solana
Step 1: Define Account State
In Solana, state is stored in accounts. Define the state structure and manage serialization using Borsh.
CosmWasm State Definition
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct State {
pub count: u32,
}
Solana State Definition
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CounterState {
pub count: u32,
}
Step 2: Reading and Writing State
In Solana, State is read from and written to account data in the program logic.
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey,
};
use borsh::BorshDeserialize;
use std::convert::TryInto;
pub fn process_increment(accounts: &[AccountInfo], program_id: &Pubkey) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let counter_account = next_account_info(account_info_iter)?;
if counter_account.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let mut counter_data = Counter::try_from_slice(&counter_account.data.borrow())?;
counter_data.count += 1;
counter_data.serialize(&mut &mut counter_account.data.borrow_mut()[..])?;
Ok(())
}
Entry Points
CosmWasm provides a modular approach with separate entry points (instantiate, execute, query) for different types of operations. Solana uses a single entry point (process_instruction) for all instruction types, which offers fine-grained control.
Key Differences
- Entry point structure
- In CosmWasm, each entry point is defined as a separate function, often
using the
#[entry_point]
attribute in Rust. - In Solana, the program internally dispatches the instruction to the appropriate handler based on teh instruction data.
- In CosmWasm, each entry point is defined as a separate function, often
using the
- Serialization and Deserialization
- CosmWasm contracts use JSON for message serialization and deserialization, which makes it easier to understand and debug but can introduce overhead
- Solana Programs use Binary Serialization for instructions, which is more efficient in terms of performance and storage.
- Error Handling
- CosmWasm typically uses standard Rust error handling methods and the errors are returned as part of the result
- Solana has a specific set of program errors and error handling is closely tied to instruction processing. Errors must be propagated correctly to ensure proper transaction behavior.
Converting CosmWasm Entry Points to Solana
CosmWasm EntryPoint Example
use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response, StdResult};
#[entry_point]
pub fn instantiate(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Initialization logic
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> StdResult<Response> {
match msg {
ExecuteMsg::Increment {} => execute_increment(deps, env, info),
ExecuteMsg::Reset { count } => execute_reset(deps, env, info, count),
}
}
#[entry_point]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetCount {} => query_count(deps),
}
}
Solana Entry Point Example
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
program_error::ProgramError,
};
use borsh::BorshDeserialize;
entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = CounterInstruction::try_from_slice(instruction_data)
.map_err(|_| ProgramError::InvalidInstructionData)?;
match instruction {
CounterInstruction::Initialize { count } => {
process_initialize(accounts, count, program_id)
}
CounterInstruction::Increment => process_increment(accounts, program_id),
CounterInstruction::Reset { count } => process_reset(accounts, count, program_id),
}
}