Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bergabman committed Jun 17, 2024
0 parents commit 5928968
Show file tree
Hide file tree
Showing 27 changed files with 1,937 additions and 0 deletions.
56 changes: 56 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release

permissions:
contents: write

# tag example: v0.0.1, v0.0.2
on:
push:
tags:
- v[0-9]+.*

jobs:
create-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1
with:
# (optional) Path to changelog.
# changelog: CHANGELOG.md
# (required) GitHub token for creating GitHub Releases.
token: ${{ secrets.GHTOKEN }}

upload-assets:
needs: create-release
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-apple-darwin
os: macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: taiki-e/upload-rust-binary-action@v1
with:
# (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload.
# Note that glob pattern is not supported yet.
bin: valid8
# (optional) Target triple, default is host triple.
# This is optional but it is recommended that this always be set to
# clarify which target you are building for if macOS is included in
# the matrix because GitHub Actions changed the default architecture
# of macos-latest since macos-14.
target: ${{ matrix.target }}
# (optional) On which platform to distribute the `.tar.gz` file.
# [default value: unix]
# [possible values: all, unix, windows, none]
tar: unix
# (optional) On which platform to distribute the `.zip` file.
# [default value: windows]
# [possible values: all, unix, windows, none]
zip: none
# (required) GitHub token for uploading assets to GitHub Releases.
token: ${{ secrets.GHTOKEN }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
target
.env
.valid8
Cargo.lock
test-ledger
33 changes: 33 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "valid8"
version = "0.0.3"
edition = "2021"
authors = ["Dean 利迪恩", "bergabman"]

[dependencies]
anyhow = "1.0.81"
base64 = "0.22.0"
bs58 = "0.5.1"
clap = { version = "4.4.8", features = ["derive", "cargo"] }
dialoguer = "0.11.0"
rayon = "1.8.0"
borsh = "1.3.1"
solana-sdk = "=1.18.1"
solana-client = "=1.18.1"
solana-ledger = "=1.18.1"
solana-runtime = "=1.18.1"
solana-account-decoder = "=1.18.1"
spl-token = "4.0.0"
serde_json = "1.0.114"
serde = { version = "1.0.197", features = ["derive"] }
anchor-lang = { version = "0.29.0", features = ["idl-build"]}
flate2 = "1.0.28"
bincode = "1.3.3"
tempfile = "3.10.1"
convert_case = "0.6"

[profile.release]
strip = true
lto = true
codegen-units = 1

108 changes: 108 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
valid8: Manage your Solana ledger locally

valid8 is a cli tool that simplifies managing Solana test ledgers for development and testing purposes. It allows you to clone accounts from the Solana blockchain and store them in a local ledger compatible with the solana-test-validator.

# Installation

(Assuming you have Rust and Cargo installed)

Clone the repository:

`git clone https://github.com/.../valid8.git`


Navigate to the project directory:
`cd valid8`

Build valid8:
`cargo build --release`

This will create a binary named valid8 in the target/release directory. You can copy the binary to a location in your PATH for easier access.

# Usage

Basic Usage:

valid8 [command]

## Available Commands:

(no argument): Opens an interactive menu for managing accounts and programs.
run : Opens the same interactive menu as no arguments
ledger (arg: overwrite): Generates a local ledger compatible with solana-test-validator.
Overwrite directory if already exists with the `-y` option.
compose: Compose multiple valid8 config files into one.

## Interactive Menu:

In the interactive menu you can choose from the following options:

Clone Account: Clone an account from the Solana blockchain.
Clone Program: Clone a program from the Solana blockchain.
Edit Accounts: Manage accounts in your local ledger.
Edit Programs: Manage programs in your local ledger.
Compose configs: Compose multiple valid8 config files into one.
Generate Ledger: Generates a local ledger compatible with solana-test-validator.
Exit: Exit the interactive menu.

Clone Account:

Select "Clone Account" from the menu.
Choose a network (mainnet or devnet) or select "Custom Network" to specify a custom RPC endpoint.
If you choose "Custom Network," provide the RPC endpoint URL when prompted.
Enter the public key of the account you want to clone.
valid8 will clone the account and store it locally.

Edit Account:

Select "Edit Account" from the menu.
Enter the public key of the account you want to edit when prompted.
Change the owner, or the amount of lamports in the account.
valid8 will edit the account and store it locally with the changed value.

Clone Program:

Select "Clone Program" from the menu.
Choose a network (mainnet or devnet) or select "Custom Network" to specify a custom RPC endpoint.
If you choose "Custom Network," provide the RPC endpoint URL when prompted.
Enter the public key of the program you want to clone, the program data account will automatically cloned.


Edit Program:

Select "Edit Program" from the menu.
Enter the public key of the program you want to edit when prompted.
Change the owner, the amount of lamports, or the upgrade authority of the program, or select Unpack PDA to edit a program related pda account
valid8 will edit the program and store it locally with the changed value(s).

Ledger Command:

`valid8 ledger`

Generates a local ledger compatible with solana-test-validator.
You can use this ledger with solana-test-validator to create a test environment and ledger with your cloned accounts and programs pre-loaded.
(use `-y` to automatically overwrite test-ledger if already exists)

Compose Command:

`valid8 compose`

Composes multiple valid8 configs together, for an even bigger dev environment.
To add an extra valid8 config and compose it with your own, just add a filename to the `compose: ` field in your `valid8.json` file.

# Example:

## Open the interactive menu
`valid8`

1. Select "Clone Account"
2. Choose "devnet" network
3. Enter the public key of the account to clone

## Generate a local ledger
`valid8 ledger -y`

## Start solana-test-validator
`solana-test-validator`

This will create a local ledger and run it with the cloned accounts available for testing.
26 changes: 26 additions & 0 deletions src/account/clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use anyhow::{anyhow, Result};
use dialoguer::Input;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;

use crate::{ common::network, context::Valid8Context };

pub fn clone(ctx: &mut Valid8Context) -> Result<()> {
let network = network::get(ctx)?;

let mut address: Option<Pubkey> = None;
while address.is_none() {
let address_string: String = Input::new()
.with_prompt("Account address")
.interact_text()?;

match Pubkey::from_str(&address_string) {
Ok(p) => address = Some(p),
Err(_) => println!("Invalid address: {}. Please enter a valid base58-encoeded Solana address.", &address_string)
}
}

let pubkey = address.ok_or(anyhow!("Public key not defined"))?;

ctx.add_account(&network, &pubkey)
}
66 changes: 66 additions & 0 deletions src/account/edit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use anyhow::{anyhow, Result};
use dialoguer::{Input, Select};
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;

use crate::context::{EditField, Valid8Context};

pub fn edit(ctx: &mut Valid8Context) -> Result<()> {

let mut address: Option<Pubkey> = None;
while address.is_none() {
let address_string: String = Input::new()
.with_prompt("Account address to edit")
.interact_text()?;

match Pubkey::from_str(&address_string) {
Ok(p) => address = Some(p),
Err(_) => println!("Invalid address: {}. Please enter a valid base58-encoeded Solana address.", &address_string)
}
}

let pubkey = address.ok_or(anyhow!("Public key not defined"))?;
if ctx.has_account(&pubkey) {
let account = ctx.accounts
.iter()
.find(|acc| acc.pubkey == pubkey)
.ok_or(anyhow!("No account found in context"))?;
// let account = ctx.accounts.get(position).ok_or(anyhow!("No account at that position"))?;

let fields: Vec<String> = vec![
format!("Owner: {}", account.owner.to_string()),
format!("Lamports: {}", account.lamports.to_string()),
format!("Unpack TokenAccount"),
format!("Unpack PDA"),
];

let selection = Select::new()
.with_prompt("Select a field to edit")
.items(&fields)
.interact()?;

match selection {
0 => {
let new_owner: Pubkey = Input::new().with_prompt("New owner pubkey").interact_text()?;
ctx.edit_account(&pubkey, EditField::Owner(new_owner))?;
},
1 => {
let new_lamports: u64 = Input::new().with_prompt("New lamports").interact_text()?;
ctx.edit_account(&pubkey, EditField::Lamports(new_lamports))?;
},
2 => {
ctx.edit_account(&pubkey, EditField::UnpackTokenAccount)?;
},
3 => {
todo!();
// ctx.edit_account(&pubkey, EditField::UnpackPDA(new_lamports))?;
},
_ => {}
}

} else {
return Err(anyhow!("Account not found in context"));
}

Ok(())
}
5 changes: 5 additions & 0 deletions src/account/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod clone;
pub use clone::*;

pub mod edit;
pub use edit::*;
9 changes: 9 additions & 0 deletions src/commands/compose.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use anyhow::Result;
use crate::context::Valid8Context;

pub fn compose(ctx: Valid8Context) -> Result<()> {
let (compose_count, account_count, program_count) = ctx.try_compose()?;

println!("✅ Valid8 configs composed! {} accounts and {} programs added from {} config(s)", account_count, program_count, compose_count);
Ok(())
}
30 changes: 30 additions & 0 deletions src/commands/edit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use anyhow::Result;
use dialoguer::Select;

use crate::{program, account, context::Valid8Context};

pub fn edit(ctx: &mut Valid8Context) -> Result<()> {
let items = vec![
"Clone program",
"Clone account",
"Edit Program",
"Edit Account"
];

let selection = Select::new()
.with_prompt("Select an option, or press Esc to exit.")
.items(&items)
.interact_opt()?;

if let Some(n) = selection {
match n {
0 => program::clone(ctx)?,
1 => account::clone(ctx)?,
2 => program::edit(ctx)?,
3 => account::edit(ctx)?,
_ => {}
}
}

Ok(())
}
34 changes: 34 additions & 0 deletions src/commands/ledger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use std::{fs, path::Path};
use anyhow::{anyhow, Result};
use dialoguer::Input;

use crate::context::Valid8Context;

pub fn ledger(ctx: Valid8Context, overwrite: &Option<String>) -> Result<()> {

let ledger_path = Path::new("test-ledger");

let mut user_choice = false;
if ledger_path.exists() {
if let Some(overwrite) = overwrite {
if overwrite.to_ascii_lowercase() == *"-y" {
user_choice = true;
}
} else {
let overwrite_choice: String = Input::new().with_prompt("Ledger path already exists, do you want to overwrite?(y/n)").interact_text()?;

match overwrite_choice.to_ascii_lowercase().as_ref() {
"y" => user_choice = true,
"n" => return Err(anyhow!("No new ledger created")),
_ => return Err(anyhow!("Incorrect option")),
}
}
}

if user_choice {
println!("Overwiting test-ledger directory");
fs::remove_dir_all(ledger_path)?;
}
ctx.create_ledger()?;
Ok(())
}
Loading

0 comments on commit 5928968

Please sign in to comment.