Skip to content

Commit

Permalink
CLVM allocator
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Sep 25, 2024
1 parent 10756b7 commit 89e4510
Show file tree
Hide file tree
Showing 9 changed files with 492 additions and 88 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ napi-derive = { workspace = true }
chia-wallet-sdk = { workspace = true }
chia = { workspace = true }
clvmr = { workspace = true }
num-bigint = { workspace = true }

[build-dependencies]
napi-build = "2.0.1"
56 changes: 48 additions & 8 deletions napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@

/* auto-generated by NAPI-RS */

export interface Pair {
first: ClvmPtr
rest: ClvmPtr
}
export interface Output {
value: ClvmPtr
cost: bigint
}
export interface Curry {
program: ClvmPtr
args: Array<ClvmPtr>
}
export interface Coin {
parentCoinInfo: Uint8Array
puzzleHash: Uint8Array
Expand Down Expand Up @@ -45,11 +57,11 @@ export interface NftMetadata {
}
export interface ParsedNft {
nftInfo: NftInfo
innerPuzzle: Uint8Array
innerPuzzle: ClvmPtr
}
export declare function parseNftInfo(puzzleReveal: Uint8Array): ParsedNft | null
export declare function parseUnspentNft(parentCoin: Coin, parentPuzzleReveal: Uint8Array, parentSolution: Uint8Array, coin: Coin): Nft | null
export declare function spendNft(nft: Nft, innerSpend: Spend): Array<CoinSpend>
export declare function parseNftInfo(clvm: ClvmAllocator, ptr: ClvmPtr): ParsedNft | null
export declare function parseUnspentNft(clvm: ClvmAllocator, parentCoin: Coin, parentPuzzleReveal: ClvmPtr, parentSolution: ClvmPtr, coin: Coin): Nft | null
export declare function spendNft(clvm: ClvmAllocator, nft: Nft, innerSpend: Spend): Array<CoinSpend>
export interface NftMint {
metadata: NftMetadata
p2PuzzleHash: Uint8Array
Expand All @@ -63,8 +75,36 @@ export interface MintedNfts {
}
export declare function mintNfts(parentCoinId: Uint8Array, nftMints: Array<NftMint>): MintedNfts
export interface Spend {
puzzle: Uint8Array
solution: Uint8Array
puzzle: ClvmPtr
solution: ClvmPtr
}
export declare function spendP2Standard(clvm: ClvmAllocator, syntheticKey: Uint8Array, conditions: Array<ClvmPtr>): Spend
export declare function spendP2Singleton(clvm: ClvmAllocator, launcherId: Uint8Array, coinId: Uint8Array, singletonInnerPuzzleHash: Uint8Array): Spend
export declare function testRoundtrip(value: bigint): bigint
export declare class ClvmAllocator {
constructor()
deserialize(value: Uint8Array): ClvmPtr
deserializeWithBackrefs(value: Uint8Array): ClvmPtr
serialize(ptr: ClvmPtr): Uint8Array
serializeWithBackrefs(ptr: ClvmPtr): Uint8Array
treeHash(ptr: ClvmPtr): Uint8Array
run(puzzle: ClvmPtr, solution: ClvmPtr, maxCost: bigint, mempoolMode: boolean): Output
curry(ptr: ClvmPtr, args: Array<ClvmPtr>): ClvmPtr
uncurry(ptr: ClvmPtr): Curry | null
newList(values: Array<ClvmPtr>): ClvmPtr
newPair(first: ClvmPtr, rest: ClvmPtr): ClvmPtr
newAtom(value: Uint8Array): ClvmPtr
newU32(value: number): ClvmPtr
newBigInt(value: bigint): ClvmPtr
list(ptr: ClvmPtr): Array<ClvmPtr>
pair(ptr: ClvmPtr): Pair | null
atom(ptr: ClvmPtr): Uint8Array | null
atomLength(ptr: ClvmPtr): number | null
u32(ptr: ClvmPtr): number | null
bigInt(ptr: ClvmPtr): bigint | null
}
export declare class ClvmPtr {
static nil(): ClvmPtr
isAtom(): boolean
isPair(): boolean
}
export declare function spendP2Standard(syntheticKey: Uint8Array, conditions: Array<Uint8Array>): Spend
export declare function spendP2Singleton(launcherId: Uint8Array, coinId: Uint8Array, singletonInnerPuzzleHash: Uint8Array): Spend
5 changes: 4 additions & 1 deletion napi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,15 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { toCoinId, parseNftInfo, parseUnspentNft, spendNft, mintNfts, spendP2Standard, spendP2Singleton } = nativeBinding
const { ClvmAllocator, ClvmPtr, toCoinId, parseNftInfo, parseUnspentNft, spendNft, mintNfts, spendP2Standard, spendP2Singleton, testRoundtrip } = nativeBinding

module.exports.ClvmAllocator = ClvmAllocator
module.exports.ClvmPtr = ClvmPtr
module.exports.toCoinId = toCoinId
module.exports.parseNftInfo = parseNftInfo
module.exports.parseUnspentNft = parseUnspentNft
module.exports.spendNft = spendNft
module.exports.mintNfts = mintNfts
module.exports.spendP2Standard = spendP2Standard
module.exports.spendP2Singleton = spendP2Singleton
module.exports.testRoundtrip = testRoundtrip
274 changes: 274 additions & 0 deletions napi/src/clvm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
use std::mem;

use chia::{
clvm_traits::{ClvmDecoder, ClvmEncoder, FromClvm, ToClvm},
clvm_utils::{tree_hash, CurriedProgram},
};
use chia_wallet_sdk::SpendContext;
use clvmr::{
run_program,
serde::{node_from_bytes, node_from_bytes_backrefs, node_to_bytes, node_to_bytes_backrefs},
ChiaDialect, NodePtr, SExp, ENABLE_BLS_OPS_OUTSIDE_GUARD, ENABLE_FIXED_DIV, MEMPOOL_MODE,
};
use napi::bindgen_prelude::*;

use crate::traits::{IntoJs, IntoRust};

#[napi]
pub struct ClvmAllocator(pub(crate) clvmr::Allocator);

impl ClvmAllocator {
pub fn with_context<T>(&mut self, f: impl FnOnce(&mut SpendContext) -> T) -> T {
let mut ctx = SpendContext::from(mem::take(&mut self.0));
let result = f(&mut ctx);
self.0 = ctx.allocator;
result
}
}

#[napi]
impl ClvmAllocator {
#[napi(constructor)]
pub fn new() -> Result<Self> {
Ok(Self(clvmr::Allocator::new()))
}

#[napi]
pub fn deserialize(&mut self, value: Uint8Array) -> Result<ClvmPtr> {
let ptr = node_from_bytes(&mut self.0, &value)?;
Ok(ClvmPtr(ptr))
}

#[napi]
pub fn deserialize_with_backrefs(&mut self, value: Uint8Array) -> Result<ClvmPtr> {
let ptr = node_from_bytes_backrefs(&mut self.0, &value)?;
Ok(ClvmPtr(ptr))
}

#[napi]
pub fn serialize(&self, ptr: &ClvmPtr) -> Result<Uint8Array> {
let bytes = node_to_bytes(&self.0, ptr.0)?;
Ok(bytes.into_js().unwrap())
}

#[napi]
pub fn serialize_with_backrefs(&self, ptr: &ClvmPtr) -> Result<Uint8Array> {
let bytes = node_to_bytes_backrefs(&self.0, ptr.0)?;
Ok(bytes.into_js().unwrap())
}

#[napi]
pub fn tree_hash(&self, ptr: &ClvmPtr) -> Result<Uint8Array> {
tree_hash(&self.0, ptr.0).to_bytes().into_js()
}

#[napi]
pub fn run(
&mut self,
env: Env,
puzzle: &ClvmPtr,
solution: &ClvmPtr,
max_cost: BigInt,
mempool_mode: bool,
) -> Result<Output> {
let mut flags = ENABLE_BLS_OPS_OUTSIDE_GUARD | ENABLE_FIXED_DIV;

if mempool_mode {
flags |= MEMPOOL_MODE;
}

let result = run_program(
&mut self.0,
&ChiaDialect::new(flags),
puzzle.0,
solution.0,
max_cost.into_rust()?,
)
.map_err(|error| Error::from_reason(error.to_string()))?;

Ok(Output {
value: ClvmPtr(result.1).into_instance(env)?,
cost: result.0.into_js()?,
})
}

#[napi]
pub fn curry(&mut self, ptr: &ClvmPtr, args: Vec<ClassInstance<ClvmPtr>>) -> Result<ClvmPtr> {
let mut args_ptr = self.0.one();

for arg in args.into_iter().rev() {
args_ptr = self
.0
.encode_curried_arg(arg.0, args_ptr)
.map_err(|error| Error::from_reason(error.to_string()))?;
}

CurriedProgram {
program: ptr.0,
args: args_ptr,
}
.to_clvm(&mut self.0)
.map_err(|error| Error::from_reason(error.to_string()))
.map(ClvmPtr)
}

#[napi]
pub fn uncurry(&self, env: Env, ptr: &ClvmPtr) -> Result<Option<Curry>> {
let Ok(value) = CurriedProgram::<NodePtr, NodePtr>::from_clvm(&self.0, ptr.0) else {
return Ok(None);
};

let mut args = Vec::new();
let mut args_ptr = value.args;

while let Ok((first, rest)) = self.0.decode_curried_arg(&args_ptr) {
args.push(ClvmPtr(first).into_instance(env)?);
args_ptr = rest;
}

if self.0.small_number(args_ptr) != Some(1) {
return Ok(None);
}

Ok(Some(Curry {
program: ClvmPtr(value.program).into_instance(env)?,
args,
}))
}

#[napi]
pub fn new_list(&mut self, values: Vec<ClassInstance<ClvmPtr>>) -> Result<ClvmPtr> {
let items: Vec<NodePtr> = values.into_iter().map(|ptr| ptr.0).collect();
let ptr = items
.to_clvm(&mut self.0)
.map_err(|error| Error::from_reason(error.to_string()))?;
Ok(ClvmPtr(ptr))
}

#[napi]
pub fn new_pair(&mut self, first: &ClvmPtr, rest: &ClvmPtr) -> Result<ClvmPtr> {
let ptr = self
.0
.new_pair(first.0, rest.0)
.map_err(|error| Error::from_reason(error.to_string()))?;
Ok(ClvmPtr(ptr))
}

#[napi]
pub fn new_atom(&mut self, value: Uint8Array) -> Result<ClvmPtr> {
let value: Vec<u8> = value.into_rust()?;
let ptr = self
.0
.new_atom(&value)
.map_err(|error| Error::from_reason(error.to_string()))?;
Ok(ClvmPtr(ptr))
}

#[napi]
pub fn new_u32(&mut self, value: u32) -> Result<ClvmPtr> {
let ptr = self
.0
.new_small_number(value)
.map_err(|error| Error::from_reason(error.to_string()))?;
Ok(ClvmPtr(ptr))
}

#[napi]
pub fn new_big_int(&mut self, value: BigInt) -> Result<ClvmPtr> {
let value = value.into_rust()?;
let ptr = self
.0
.new_number(value)
.map_err(|error| Error::from_reason(error.to_string()))?;
Ok(ClvmPtr(ptr))
}

#[napi]
pub fn list(&self, env: Env, ptr: &ClvmPtr) -> Result<Vec<ClassInstance<ClvmPtr>>> {
let items = Vec::<NodePtr>::from_clvm(&self.0, ptr.0)
.map_err(|error| Error::from_reason(error.to_string()))?;
items
.into_iter()
.map(|ptr| ClvmPtr(ptr).into_instance(env))
.collect()
}

#[napi]
pub fn pair(&self, env: Env, ptr: &ClvmPtr) -> Result<Option<Pair>> {
let SExp::Pair(first, rest) = self.0.sexp(ptr.0) else {
return Ok(None);
};
Ok(Some(Pair {
first: ClvmPtr(first).into_instance(env)?,
rest: ClvmPtr(rest).into_instance(env)?,
}))
}

#[napi]
pub fn atom(&self, ptr: &ClvmPtr) -> Option<Uint8Array> {
match self.0.sexp(ptr.0) {
SExp::Atom => Some(self.0.atom(ptr.0).as_ref().to_vec().into_js().unwrap()),
SExp::Pair(..) => None,
}
}

#[napi]
pub fn atom_length(&self, ptr: &ClvmPtr) -> Option<u32> {
match self.0.sexp(ptr.0) {
SExp::Atom => self.0.atom_len(ptr.0).try_into().ok(),
SExp::Pair(..) => None,
}
}

#[napi]
pub fn u32(&self, ptr: &ClvmPtr) -> Option<u32> {
self.0.small_number(ptr.0)
}

#[napi]
pub fn big_int(&self, ptr: &ClvmPtr) -> Option<BigInt> {
match self.0.sexp(ptr.0) {
SExp::Atom => Some(self.0.number(ptr.0).into_js().unwrap()),
SExp::Pair(..) => None,
}
}
}

#[napi]
pub struct ClvmPtr(pub(crate) clvmr::NodePtr);

#[napi]
impl ClvmPtr {
#[napi(factory)]
pub fn nil() -> Self {
Self(NodePtr::NIL)
}

#[napi]
pub fn is_atom(&self) -> bool {
self.0.is_atom()
}

#[napi]
pub fn is_pair(&self) -> bool {
self.0.is_pair()
}
}

#[napi(object)]
pub struct Pair {
pub first: ClassInstance<ClvmPtr>,
pub rest: ClassInstance<ClvmPtr>,
}

#[napi(object)]
pub struct Output {
pub value: ClassInstance<ClvmPtr>,
pub cost: BigInt,
}

#[napi(object)]
pub struct Curry {
pub program: ClassInstance<ClvmPtr>,
pub args: Vec<ClassInstance<ClvmPtr>>,
}
Loading

0 comments on commit 89e4510

Please sign in to comment.