3.1 Defining Contract Messages

Each platform has a different approach to defining the interface for interacting with contracts:

CosmWasm - Enum Messages

use cosmwasm_schema::{cw_serde, QueryResponses};

// All execute operations in one enum
#[cw_serde]
pub enum ExecuteMsg {
    // Offer operations
    CreateOffer {
        offer_type: OfferType,
        fiat_currency: FiatCurrency,
        min_amount: Uint128,
        max_amount: Uint128,
        rate: Decimal,
    },
    UpdateOffer {
        id: u64,
        rate: Option<Decimal>,
        min_amount: Option<Uint128>,
        max_amount: Option<Uint128>,
    },
    PauseOffer { id: u64 },
    ResumeOffer { id: u64 },
    DeleteOffer { id: u64 },
}

// Query messages with response types
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
    #[returns(Offer)]
    Offer { id: u64 },

    #[returns(Vec<Offer>)]
    OffersByOwner {
        owner: String,
        limit: Option<u32>,
    },
}

Solidity - Function Signatures

interface IOffer {
    // Events define the ABI
    event OfferCreated(
        uint256 indexed id,
        address indexed owner,
        OfferType offerType
    );

    // Offer operations as functions
    function createOffer(
        OfferType offerType,
        string calldata fiatCurrency,
        uint256 minAmount,
        uint256 maxAmount,
        uint256 rate
    ) external returns (uint256);

    function updateOffer(
        uint256 id,
        uint256 rate,
        uint256 minAmount,
        uint256 maxAmount
    ) external;

    function pauseOffer(uint256 id) external;
    function resumeOffer(uint256 id) external;
    function deleteOffer(uint256 id) external;

    // View functions (queries)
    function getOffer(uint256 id)
        external view returns (OfferData memory);

    function getOffersByOwner(address owner)
        external view returns (uint256[] memory);
}

Solana - Anchor Instructions

#[program]
pub mod offer {
    use super::*;

    // Each instruction is a function
    pub fn create_offer(
        ctx: Context<CreateOffer>,
        offer_type: OfferType,
        fiat_currency: [u8; 3],
        min_amount: u64,
        max_amount: u64,
        rate: u64,
    ) -> Result<()> { ... }

    pub fn update_offer(
        ctx: Context<UpdateOffer>,
        rate: Option<u64>,
        min_amount: Option<u64>,
        max_amount: Option<u64>,
    ) -> Result<()> { ... }

    pub fn pause_offer(
        ctx: Context<PauseOffer>,
    ) -> Result<()> { ... }

    pub fn resume_offer(
        ctx: Context<ResumeOffer>,
    ) -> Result<()> { ... }

    pub fn delete_offer(
        ctx: Context<DeleteOffer>,
    ) -> Result<()> { ... }
}

// No query functions - reads are client-side

Pattern Comparison

CosmWasm uses a single enum to define all messages, with pattern matching to route them. Solidity uses individual functions with visibility modifiers. Anchor uses functions decorated with #[program], each with its own account context struct.

3.2 Processing Messages

How each platform routes incoming messages to handlers:

CosmWasm - Pattern Matching

#[entry_point]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    // Route to handler based on variant
    match msg {
        ExecuteMsg::CreateOffer {
            offer_type,
            fiat_currency,
            min_amount,
            max_amount,
            rate,
        } => execute_create_offer(
            deps, env, info,
            offer_type, fiat_currency,
            min_amount, max_amount, rate
        ),

        ExecuteMsg::UpdateOffer {
            id, rate, min_amount, max_amount
        } => execute_update_offer(
            deps, info, id, rate,
            min_amount, max_amount
        ),

        ExecuteMsg::PauseOffer { id } =>
            execute_pause_offer(deps, info, id),

        ExecuteMsg::ResumeOffer { id } =>
            execute_resume_offer(deps, info, id),

        ExecuteMsg::DeleteOffer { id } =>
            execute_delete_offer(deps, info, id),
    }
}

Solidity - Direct Dispatch

contract Offer is IOffer {
    // EVM routes by function selector
    // (first 4 bytes of keccak256(sig))

    function createOffer(
        OfferType offerType,
        string calldata fiatCurrency,
        uint256 minAmount,
        uint256 maxAmount,
        uint256 rate
    ) external
        whenNotPaused
        nonReentrant
    returns (uint256) {
        // Validate inputs
        require(
            minAmount < maxAmount,
            "Invalid amounts"
        );
        require(rate > 0, "Invalid rate");

        // Create offer...
        uint256 id = _createOffer(
            msg.sender,
            offerType,
            fiatCurrency,
            minAmount,
            maxAmount,
            rate
        );

        emit OfferCreated(id, msg.sender, offerType);
        return id;
    }

    // Each function is independently callable
    function pauseOffer(uint256 id)
        external onlyOfferOwner(id)
    {
        offers[id].state = OfferState.Paused;
        emit OfferPaused(id);
    }
}

Solana - Instruction Discriminator

// Anchor generates discriminators from
// function names (first 8 bytes of sha256)

#[program]
pub mod offer {
    pub fn create_offer(
        ctx: Context<CreateOffer>,
        offer_type: OfferType,
        fiat_currency: [u8; 3],
        min_amount: u64,
        max_amount: u64,
        rate: u64,
    ) -> Result<()> {
        // Validate
        require!(
            min_amount < max_amount,
            OfferError::InvalidAmounts
        );
        require!(rate > 0, OfferError::InvalidRate);

        // Initialize offer account
        let offer = &mut ctx.accounts.offer;
        offer.owner = ctx.accounts.owner.key();
        offer.offer_type = offer_type;
        offer.fiat_currency = fiat_currency;
        offer.min_amount = min_amount;
        offer.max_amount = max_amount;
        offer.rate = rate;
        offer.state = OfferState::Active;

        // Emit event
        emit!(OfferCreated {
            id: offer.id,
            owner: offer.owner,
            offer_type,
        });

        Ok(())
    }
}

3.3 Solana Account Contexts

Solana's most unique feature is the Account Context - defining ALL accounts an instruction needs upfront:

CosmWasm - Implicit Access

// CosmWasm accesses storage implicitly
// through deps.storage

pub fn execute_update_offer(
    deps: DepsMut,
    info: MessageInfo,
    id: u64,
    rate: Option<Decimal>,
    min_amount: Option<Uint128>,
    max_amount: Option<Uint128>,
) -> Result<Response, ContractError> {
    // Load offer from storage
    let mut offer = OFFERS.load(
        deps.storage, id
    )?;

    // Check ownership
    if offer.owner != info.sender {
        return Err(
            ContractError::Unauthorized {}
        );
    }

    // Update fields
    if let Some(r) = rate {
        offer.rate = r;
    }
    if let Some(min) = min_amount {
        offer.min_amount = min;
    }
    if let Some(max) = max_amount {
        offer.max_amount = max;
    }

    // Save back
    OFFERS.save(deps.storage, id, &offer)?;

    Ok(Response::new())
}

Solidity - Implicit Access

// Solidity accesses storage implicitly
// through state variables

function updateOffer(
    uint256 id,
    uint256 rate,
    uint256 minAmount,
    uint256 maxAmount
) external {
    // Load from mapping
    OfferData storage offer = offers[id];

    // Check ownership
    require(
        offer.owner == msg.sender,
        "Not offer owner"
    );

    // Update fields (0 = no change)
    if (rate > 0) {
        offer.rate = rate;
    }
    if (minAmount > 0) {
        offer.minAmount = minAmount;
    }
    if (maxAmount > 0) {
        offer.maxAmount = maxAmount;
    }

    // Storage automatically persisted
    emit OfferUpdated(id);
}

Solana - Explicit Accounts

// ALL accounts must be declared upfront!

#[derive(Accounts)]
pub struct UpdateOffer<'info> {
    // Signer (owner)
    #[account(mut)]
    pub owner: Signer<'info>,

    // The offer account to update
    #[account(
        mut,
        seeds = [
            b"offer",
            owner.key().as_ref(),
            &offer.id.to_le_bytes()
        ],
        bump = offer.bump,
        has_one = owner @ OfferError::NotOwner
    )]
    pub offer: Account<'info, Offer>,
}

pub fn update_offer(
    ctx: Context<UpdateOffer>,
    rate: Option<u64>,
    min_amount: Option<u64>,
    max_amount: Option<u64>,
) -> Result<()> {
    // Account validation done by derive!
    // Owner check: has_one = owner
    // PDA validation: seeds + bump

    let offer = &mut ctx.accounts.offer;

    if let Some(r) = rate {
        offer.rate = r;
    }
    if let Some(min) = min_amount {
        offer.min_amount = min;
    }
    if let Some(max) = max_amount {
        offer.max_amount = max;
    }

    Ok(())
}
💡
Anchor Constraints

Anchor provides powerful declarative constraints like has_one, constraint, and seeds that validate accounts before your instruction runs. This moves validation from imperative code to declarations.

3.4 Input Validation

CosmWasm Validation

pub fn execute_create_offer(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: CreateOfferMsg,
) -> Result<Response, ContractError> {
    // Query hub for config
    let hub_config: HubConfig = deps.querier
        .query_wasm_smart(
            hub_addr,
            &HubQueryMsg::Config {}
        )?;

    // Validate amounts
    if msg.min_amount >= msg.max_amount {
        return Err(
            ContractError::InvalidAmounts {}
        );
    }

    // Check against hub limits
    if msg.min_amount < hub_config.min_trade_amount {
        return Err(
            ContractError::AmountTooSmall {}
        );
    }

    // Validate rate
    if msg.rate.is_zero() {
        return Err(
            ContractError::InvalidRate {}
        );
    }

    // Validate fiat currency
    if !FiatCurrency::is_valid(&msg.fiat_currency) {
        return Err(
            ContractError::InvalidFiatCurrency {}
        );
    }

    // Proceed with creation...
    Ok(Response::new())
}

Solidity Validation

contract Offer {
    // Custom errors (gas efficient)
    error InvalidAmounts();
    error AmountTooSmall(uint256 min);
    error InvalidRate();

    function createOffer(
        OfferType offerType,
        string calldata fiatCurrency,
        uint256 minAmount,
        uint256 maxAmount,
        uint256 rate
    ) external returns (uint256) {
        // Get hub config
        IHub.HubConfig memory hubConfig =
            IHub(hub).getConfig();

        // Validate with require
        if (minAmount >= maxAmount) {
            revert InvalidAmounts();
        }

        if (minAmount < hubConfig.minTradeAmount) {
            revert AmountTooSmall(
                hubConfig.minTradeAmount
            );
        }

        if (rate == 0) {
            revert InvalidRate();
        }

        // Validate fiat (length check)
        require(
            bytes(fiatCurrency).length == 3,
            "Invalid fiat code"
        );

        // Proceed...
    }
}

Solana Validation

// Error enum
#[error_code]
pub enum OfferError {
    #[msg("Min amount must be less than max")]
    InvalidAmounts,
    #[msg("Amount below minimum")]
    AmountTooSmall,
    #[msg("Rate must be positive")]
    InvalidRate,
    #[msg("Not the offer owner")]
    NotOwner,
}

#[derive(Accounts)]
pub struct CreateOffer<'info> {
    // Hub config for validation
    #[account(
        seeds = [b"hub_config"],
        bump = hub_config.bump,
        seeds::program = hub_program.key()
    )]
    pub hub_config: Account<'info, HubConfig>,

    // Other accounts...
}

pub fn create_offer(
    ctx: Context<CreateOffer>,
    min_amount: u64,
    max_amount: u64,
    rate: u64,
) -> Result<()> {
    let hub = &ctx.accounts.hub_config;

    // Validate with require!
    require!(
        min_amount < max_amount,
        OfferError::InvalidAmounts
    );

    require!(
        min_amount >= hub.min_trade_amount,
        OfferError::AmountTooSmall
    );

    require!(rate > 0, OfferError::InvalidRate);

    // Proceed...
    Ok(())
}
Validation Aspect CosmWasm Solidity Solana
Error Type Custom enum with thiserror Custom errors or require #[error_code] enum
Return Early return Err(...) revert / require require! macro
Cross-contract Query deps.querier.query_wasm_smart Direct interface call Pass account in context

Knowledge Check

Test Your Understanding

1. What is a "discriminator" in Anchor?

2. Why must Solana instructions declare all accounts upfront?

3. What does has_one = owner do in an Anchor account constraint?