Skip to content

Commit

Permalink
Add example Solana contract using the sealable trie
Browse files Browse the repository at this point in the history
Introduce solana/trie-example smart contract which uses the sealable
trie on Solana chain.  The contract as well as its client are quite
rudimentary but they allow demonstrating working code.
  • Loading branch information
mina86 committed Aug 21, 2023
1 parent 5f3c85f commit c455ad4
Show file tree
Hide file tree
Showing 15 changed files with 922 additions and 4 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
target/
/target/
/dist/
/node_modules/
package-lock.json
Cargo.lock
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ rust-version = "1.71.0"
members = [
"memory",
"sealable-trie",
"solana/trie-example",
"stdx",
]
resolver = "2"
Expand All @@ -18,6 +19,9 @@ derive_more = "0.99.17"
pretty_assertions = "1.4.0"
rand = { version = "0.8.5" }
sha2 = { version = "0.10.7", default-features = false }
solana-client = "1.16.7"
solana-program = "1.16.7"
solana-sdk = "1.16.7"
strum = { version = "0.25.0", default-features = false, features = ["derive"] }

memory = { path = "memory" }
Expand Down
5 changes: 4 additions & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []

[bans]
multiple-versions = "deny"
# solana-program is weird and has bunch of duplicate dependencies.
# For now allow duplicates. TODO(mina86): Figure out if there’s
# something better we can do.
#multiple-versions = "deny"
skip = [
# derive_more still uses old syn
{ name = "syn", version = "1.0.*" },
Expand Down
43 changes: 43 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "solana-trie-example",
"version": "0.0.1",
"author": "Michał Nazarewicz <[email protected]>",
"scripts": {
"start": "ts-node solana/trie-example/client/main.ts",
"start-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health start",
"lint": "eslint --ext .ts solana/trie-example/* && prettier --check \"solana/trie-example/**/*.ts\"",
"lint:fix": "eslint --ext .ts solana/trie-example/* --fix && prettier --write \"solana/trie-example/**/*.ts\"",
"clean": "npm run clean:trie-example",
"build:trie-example": "cargo build-sbf --manifest-path=solana/trie-example/Cargo.toml --sbf-out-dir=dist/trie-example",
"deploy:trie-example": "solana program deploy dist/trie-example/trie.so",
"clean:trie-example": "cargo clean --manifest-path=solana/trie-example/Cargo.toml && rm -rf ./dist",
"test:trie-example": "cargo test-bpf --manifest-path=solana/trie-example/Cargo.toml",
"pretty": "prettier --write 'solana/trie-example/client/*.ts'"
},
"dependencies": {
"@solana/web3.js": "^1.33.0",
"mz": "^2.7.0",
"tsconfig": "^7.0.0",
"yaml": "^2.0.0"
},
"devDependencies": {
"@tsconfig/recommended": "^1.0.1",
"@types/eslint": "^8.2.2",
"@types/eslint-plugin-prettier": "^3.1.0",
"@types/mz": "^2.7.2",
"@types/prettier": "^2.1.5",
"@types/yaml": "^1.9.7",
"@typescript-eslint/eslint-plugin": "^4.6.0",
"@typescript-eslint/parser": "^4.6.0",
"eslint": "^7.12.1",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.1.2",
"start-server-and-test": "^1.11.6",
"ts-node": "^10.0.0",
"typescript": "^4.0.5"
},
"engines": {
"node": ">=14.0.0"
}
}
2 changes: 1 addition & 1 deletion sealable-trie/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ pub mod trie;
#[cfg(test)]
mod test_utils;

pub use trie::Trie;
pub use trie::{Error, Trie};
21 changes: 20 additions & 1 deletion sealable-trie/src/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ macro_rules! proof {
}

impl<A: memory::Allocator<Value = Value>> Trie<A> {
/// Creates a new trie using given allocator.
/// Creates a new empty trie using given allocator.
pub fn new(alloc: A) -> Self {
Self { root_ptr: None, root_hash: EMPTY_TRIE_ROOT, alloc }
}
Expand All @@ -117,6 +117,25 @@ impl<A: memory::Allocator<Value = Value>> Trie<A> {
/// Returns whether the trie is empty.
pub fn is_empty(&self) -> bool { self.root_hash == EMPTY_TRIE_ROOT }

/// Deconstructs the object into the individual parts — allocator, root
/// pointer and root hash.
pub fn into_parts(self) -> (A, Option<Ptr>, CryptoHash) {
(self.alloc, self.root_ptr, self.root_hash)
}

/// Creates a new trie from individual parts.
///
/// It’s up to the caller to guarantee that the `root_ptr` and `root_hash`
/// values are correct and correspond to nodes stored within the pool
/// allocator `alloc`.
pub fn from_parts(
alloc: A,
root_ptr: Option<Ptr>,
root_hash: CryptoHash,
) -> Self {
Self { root_ptr, root_hash, alloc }
}

/// Retrieves value at given key.
///
/// Returns `None` if there’s no value at given key. Returns an error if
Expand Down
17 changes: 17 additions & 0 deletions solana/trie-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "solana-trie-example"
authors = ["Michal Nazarewicz <[email protected]>"]
version = "0.0.0"
edition = "2021"

[lib]
name = "trie"
crate-type = ["cdylib", "lib"]

[dependencies]
derive_more.workspace = true
solana-program.workspace = true

memory.workspace = true
sealable-trie.workspace = true
stdx.workspace = true
9 changes: 9 additions & 0 deletions solana/trie-example/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh

set -eu

root=$(git rev-parse --show-toplevel)
cd "$root"
cargo build-sbf \
--manifest-path=solana/trie-example/Cargo.toml \
--sbf-out-dir=dist/trie-example
75 changes: 75 additions & 0 deletions solana/trie-example/client/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
establishConnection,
establishPayer,
checkProgram,
getKey,
setKey,
sealKey,
} from './trie';

async function main() {
const operation = parseArgv(process.argv);
if (!operation) {
return;
}

// Establish connection to the cluster
await establishConnection();

// Determine who pays for the fees
await establishPayer();

// Check if the program has been deployed
await checkProgram();

switch (operation[0]) {
case "get":
await getKey(operation[1]);
break;
case "set":
await setKey(operation[1], operation[2]);
break;
case "seal":
await sealKey(operation[1]);
break;
}

console.log('Success');
}

function parseArgv(argv: string[]): string[] | null {
const cmd = argv[0] + ' ' + argv[1];
switch (argv[2] || "--help") {
case "get":
case "seal":
if (argv.length != 4) {
break;
}
return [argv[2], argv[3]];
case "set":
if (argv.length != 5) {
break;
}
return [argv[2], argv[3], argv[4]];
case "help":
case "--help":
case "-h": {
console.log(
`usage: ${cmd} get <hex-key>\n` +
` ${cmd} set <hex-key> <hex-hash>\n` +
` ${cmd} seal <hex-key>`
)
process.exit(0);
}
}
console.error(`Invalid usage; see ${cmd} --help`);
process.exit(-1);
}

main().then(
() => process.exit(),
err => {
console.error(err);
process.exit(-1);
},
);
Loading

0 comments on commit c455ad4

Please sign in to comment.