Skip to content

Solana smart contract framework.

Notifications You must be signed in to change notification settings

thewuhxyz/steel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ—οΈ Steel

Steel is a new Solana smart contract framework. It provides a library of helper functions, macros, and code patterns for building safe and maintainable smart contracts on the Solana blockchain.

Notes

  • Steel is under active development. All interfaces are subject to change.
  • This code is unaudited. Use at your own risk!

Todos

  • Localnet toolchain.
  • Mainnet toolchain.
  • IDL generation.
  • Helper functions for simple lamport transfers.
  • Helper functions to emit events (wrap sol_log_data).
  • Custom error messages on account validation checks.
  • Helper function to close AccountInfos.
  • CLI with init script.
  • Account parsers and validation.

Getting started

To get started, install the CLI:

cargo install steel-cli

Spin up a new project with the new command:

steel new my-project

Compile your program using the Solana toolchain:

cargo build-sbf

Test your program using the Solana toolchain:

cargo test-sbf

File structure

While not strictly enforced, we recommend organizing your Solana program with the following file structure. We have found this pattern to improve code readability, separating the contract interface from its implementation. It scales well for complex contracts.

Cargo.toml (workspace)
βŒ™ api
  βŒ™ Cargo.toml
  βŒ™ src
    βŒ™ consts.rs
    βŒ™ error.rs
    βŒ™ event.rs
    βŒ™ instruction.rs
    βŒ™ lib.rs
    βŒ™ loaders.rs
    βŒ™ sdk.rs
    βŒ™ state
      βŒ™ mod.rs
      βŒ™ account_1.rs
      βŒ™ account_2.rs
βŒ™ program
  βŒ™ Cargo.toml
  βŒ™ src
    βŒ™ lib.rs
    βŒ™ instruction_1.rs
    βŒ™ instruction_2.rs

API

Accounts

Use the account! macro to link account structs with the discriminator and implement basic serialization logic.

use steel::*;

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum MyAccount {
    Counter = 0,
    Profile = 1,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Counter {
    pub value: u64,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Profile {
    pub id: u64,
}

account!(MyAccount, Counter);

Instructions

Use the instruction! macro to link instruction data with the discriminator and implement basic serialization logic.

use steel::*;

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
pub enum MyInstruction {
    Initialize = 0,
    Add = 1,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Initialize {}

#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Add {
    pub value: u64,
}

instruction!(MyInstruction, Initialize);
instruction!(MyInstruction, Add);

Errors

Use the error! macro to define custom errors.

use steel::*;

/// Enum for error types.
#[repr(u32)]
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)]
pub enum MyError {
    #[error("You did something wrong")]
    Dummy = 0,
}

error!(MyError);

Events

Use the event! macro to define custom events.

use steel::*;

/// Struct for logged events.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct MyEvent {
    pub value: u64,
}

event!(MyEvent);

Program

Entrypoint

Use the entrypoint! macro to streamline the program entrypoint.

mod add;
mod initialize;

use add::*;
use initialize::*;

use example_api::prelude::*;
use steel::*;

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    data: &[u8],
) -> ProgramResult {
    let (ix, data) = parse_instruction::<MyInstruction>(&example_api::ID, program_id, data)?;

    match ix {
        MyInstruction::Initialize => process_initialize(accounts, data)?,
        MyInstruction::Add => process_add(accounts, data)?,
    }

    Ok(())
}

entrypoint!(process_instruction);

Validation

Use chainable parsers and assertion functions to validate account data.

use example_api::prelude::*;
use steel::*;

pub fn process_add(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
    // Load accounts.
    let [signer_info, counter_info] = accounts else {
        return Err(ProgramError::NotEnoughAccountKeys);
    };

    // Validate signer.
    signer_info.is_signer()?;

    // Parse and validate account.
    let counter = counter_info
        .as_account_mut::<Counter>(&example_api::ID)? 
        .assert_mut(|c| c.value <= 42)?;

    // Update state.
    counter.value += 1;

    // Return.
    Ok(())
}

CPIs

Use streamlined helpers for executing common tasks like creating accounts and transferring tokens.

use steel::*;

pub fn process_transfer(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
    // Load accounts.
    let [signer_info, mint_info, sender_info, receiver_info, token_program] = accounts else {
        return Err(ProgramError::NotEnoughAccountKeys);
    };

    signer_info.is_signer()?;

    mint_info.as_mint()?;

    sender_info
        .is_writable()?
        .as_token_account()?
        .assert(|t| t.owner == *signer_info.key)?
        .assert(|t| t.mint == *mint_info.key)?;

    receiver_info
        .is_writable()?
        .as_token_account()?
        .assert(|t| t.mint == *mint_info.key)?;

    token_program.is_program(&spl_token::ID)?;

    // Transfer tokens.
    let amount = 42;
    transfer(
        signer_info,
        sender_info,
        receiver_info,
        token_program,
        amount,
    )?;

    // Return.
    Ok(())
}

About

Solana smart contract framework.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 100.0%