Skip to content

Commit 2f244bf

Browse files
authored
add steel example to close account (#208)
1 parent 5bdafb4 commit 2f244bf

25 files changed

+1630
-2
lines changed

basics/close-account/steel/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target
2+
test-ledger

basics/close-account/steel/Cargo.toml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[workspace]
2+
members = ["api", "program"]
3+
resolver = "2"
4+
5+
[workspace.package]
6+
version = "0.1.0"
7+
edition = "2021"
8+
license = "Apache-2.0"
9+
homepage = "https://github.com/solana-developers/program-examples"
10+
documentation = "https://github.com/solana-developers/program-examples"
11+
respository = "https://github.com/solana-developers/program-examples"
12+
readme = "./README.md"
13+
keywords = ["solana"]
14+
15+
[workspace.dependencies]
16+
close-account-api = { path = "./api", version = "0.1.0" }
17+
bytemuck = "1.14"
18+
num_enum = "0.7"
19+
solana-program = "1.18"
20+
steel = "2.1.0"
21+
thiserror = "1.0"

basics/close-account/steel/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# CloseAccount
2+
3+
**CloseAccount** is a ...
4+
5+
## API
6+
- [`Consts`](api/src/consts.rs) – Program constants.
7+
- [`Error`](api/src/error.rs) – Custom program errors.
8+
- [`Event`](api/src/event.rs) – Custom program events.
9+
- [`Instruction`](api/src/instruction.rs) – Declared instructions.
10+
11+
## Instructions
12+
- [`Hello`](program/src/hello.rs) – Hello ...
13+
14+
## State
15+
- [`User`](api/src/state/user.rs) – User ...
16+
17+
## Tests
18+
19+
To run the test suit, use the Solana toolchain:
20+
```
21+
cargo test-sbf
22+
```
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "close-account-api"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
bytemuck.workspace = true
8+
jzon = "0.12.5"
9+
num_enum.workspace = true
10+
solana-program.workspace = true
11+
steel.workspace = true
12+
thiserror.workspace = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use steel::*;
2+
3+
/// A [Result] type representing `Result<T, CloseAccountError>`
4+
pub type CloseAccountResult<T> = Result<T, CloseAccountError>;
5+
6+
/// Error handling enum for this create
7+
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)]
8+
#[repr(u32)]
9+
pub enum CloseAccountError {
10+
/// A name can only be 64 bytes in length when converted to bytes
11+
#[error("Invalid Name Length. The maximum length of the string is 64 bytes.")]
12+
MaxNameLengthExceeded = 0,
13+
/// Only UTF-8 String types are supported
14+
#[error("Only UTF-8 String encoding is supported")]
15+
OnlyUtf8IsSupported = 1,
16+
}
17+
18+
error!(CloseAccountError);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use steel::*;
2+
3+
/// Used in generating the discriminats for instructions
4+
#[repr(u8)]
5+
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
6+
pub enum MyInstruction {
7+
/// Create account discriminant represented by `0`
8+
CreateAccount = 0,
9+
/// Close account discriminant represented by `1`
10+
CloseAccount = 1,
11+
}
12+
13+
/// Create account struct with the name
14+
/// as an array of 64 bytes
15+
#[repr(C)]
16+
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
17+
pub struct CreateAccount(pub [u8; 64]);
18+
19+
/// UsedClose Account
20+
#[repr(C)]
21+
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
22+
pub struct CloseAccount;
23+
24+
instruction!(MyInstruction, CreateAccount);
25+
instruction!(MyInstruction, CloseAccount);
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![forbid(unsafe_code)]
2+
3+
pub mod error;
4+
pub mod instruction;
5+
pub mod sdk;
6+
pub mod state;
7+
8+
pub mod prelude {
9+
pub use crate::error::*;
10+
pub use crate::instruction::*;
11+
pub use crate::sdk::*;
12+
pub use crate::state::*;
13+
}
14+
15+
use steel::*;
16+
17+
// Set program id
18+
declare_id!("z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35");
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use steel::*;
2+
3+
use crate::prelude::*;
4+
5+
/// Create an PDA and store a String in it
6+
pub fn create_account(signer: Pubkey, user: CreateAccount) -> Instruction {
7+
Instruction {
8+
program_id: crate::ID,
9+
accounts: vec![
10+
AccountMeta::new(signer, true),
11+
AccountMeta::new(User::pda(signer).0, false),
12+
AccountMeta::new_readonly(system_program::ID, false),
13+
],
14+
data: user.to_bytes(),
15+
}
16+
}
17+
18+
/// Creates an instruction to close the account,
19+
/// in our case the PDA. The PDA address is derived from
20+
/// the `payer` public key
21+
pub fn close_account(signer: Pubkey) -> Instruction {
22+
Instruction {
23+
program_id: crate::ID,
24+
accounts: vec![
25+
AccountMeta::new(signer, true),
26+
AccountMeta::new(User::pda(signer).0, false),
27+
AccountMeta::new_readonly(system_program::ID, false),
28+
],
29+
data: CloseAccount.to_bytes(),
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod user;
2+
pub use user::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use steel::*;
2+
3+
use crate::error::{CloseAccountError, CloseAccountResult};
4+
5+
/// An enum which is used to derive a discriminator for the user account.
6+
#[repr(u8)]
7+
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
8+
pub enum UserAccount {
9+
/// The user is represented by a discriminator of `0`
10+
User = 0,
11+
}
12+
13+
/// The user Account structure which stores a
14+
/// `name` as bytes with max array length of u64 due to the
15+
/// requirement for memory alignment since 64 is a factor of 8.
16+
#[repr(C)]
17+
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
18+
pub struct User {
19+
/// The name string stored as bytes.
20+
/// The `&str` is converted into bytes and copied upto
21+
/// the length of the bytes, if the bytes are not 64, it
22+
/// pads with zeroes upto 64, if it is more than 64 an error
23+
/// is returned.
24+
pub name: [u8; 64],
25+
}
26+
27+
impl User {
28+
/// Seed for the [User] used to in PDA generation
29+
pub const SEED_PREFIX: &'static str = "USER";
30+
31+
/// Create a new user, convert the name into bytes
32+
/// and add those bytes to a 64 byte array
33+
pub fn new(name: &str) -> CloseAccountResult<Self> {
34+
let name_bytes = name.as_bytes();
35+
36+
Self::check_length(name_bytes)?;
37+
38+
let mut name = [0u8; 64];
39+
name[0..name_bytes.len()].copy_from_slice(name_bytes);
40+
41+
Ok(Self { name })
42+
}
43+
44+
/// Converts the byte array into a UTF-8 [str]
45+
/// using the `trim_end_matches("\0")` of [str] method
46+
/// to remove padded zeroes if any. Padded zeroes are
47+
/// represented by `\0`
48+
pub fn to_string(&self) -> CloseAccountResult<String> {
49+
let value =
50+
core::str::from_utf8(&self.name).map_err(|_| CloseAccountError::OnlyUtf8IsSupported)?;
51+
52+
Ok(value.trim_end_matches("\0").to_string())
53+
}
54+
55+
fn check_length(bytes: &[u8]) -> CloseAccountResult<()> {
56+
if bytes.len() > 64 {
57+
return Err(CloseAccountError::MaxNameLengthExceeded);
58+
}
59+
60+
Ok(())
61+
}
62+
63+
/// Generate a PDA from the [Self::SEED_PREFIX] constant
64+
/// and the payer public key. This returns a tuple struct
65+
/// ([Pubkey], [u8])
66+
pub fn pda(payer: Pubkey) -> (Pubkey, u8) {
67+
Pubkey::try_find_program_address(
68+
&[Self::SEED_PREFIX.as_bytes(), payer.as_ref()],
69+
&crate::id(),
70+
)
71+
.unwrap()
72+
}
73+
}
74+
75+
account!(UserAccount, User);

basics/close-account/steel/cicd.sh

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
# This script is for quick building & deploying of the program.
4+
# It also serves as a reference for the commands used for building & deploying Solana programs.
5+
# Run this bad boy with "bash cicd.sh" or "./cicd.sh"
6+
7+
cargo build-sbf --manifest-path=./program/Cargo.toml --bpf-out-dir=./program/target/so
8+
solana program deploy ./program/target/so/program.so
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"scripts": {
3+
"test": "pnpm ts-mocha -p ./tests/tsconfig.test.json -t 1000000 ./tests/close-account.test.ts",
4+
"build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures && pnpm test",
5+
"build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./target/so",
6+
"deploy": "solana program deploy ./target/so/close_account_program.so"
7+
},
8+
"dependencies": {
9+
"@coral-xyz/borsh": "^0.30.1",
10+
"@solana/web3.js": "^1.35"
11+
},
12+
"devDependencies": {
13+
"solana-bankrun": "^0.4.0",
14+
"typescript": "^5.6.3",
15+
"ts-mocha": "^10.0.0"
16+
},
17+
"packageManager": "[email protected]+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1"
18+
}

0 commit comments

Comments
 (0)