From 470bf5f9001b9cff427bc8ff4ae3a40bb2e16503 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Mon, 30 Sep 2024 21:36:22 -0400 Subject: [PATCH] Conditions --- napi/index.d.ts | 43 +++++- napi/src/clvm.rs | 329 ++++++++++++++++++++++------------------- napi/src/clvm_value.rs | 126 ++++++++++++++++ napi/src/lib.rs | 1 + napi/src/traits.rs | 57 ++++++- 5 files changed, 393 insertions(+), 163 deletions(-) create mode 100644 napi/src/clvm_value.rs diff --git a/napi/index.d.ts b/napi/index.d.ts index b5ffa554..64d7d980 100644 --- a/napi/index.d.ts +++ b/napi/index.d.ts @@ -83,6 +83,41 @@ export declare function fromHexRaw(hex: string): Uint8Array export declare function fromHex(hex: string): Uint8Array export declare function toHex(bytes: Uint8Array): string export declare class ClvmAllocator { + remark(value: Program): Program + aggSigParent(publicKey: Uint8Array, message: Uint8Array): Program + aggSigPuzzle(publicKey: Uint8Array, message: Uint8Array): Program + aggSigAmount(publicKey: Uint8Array, message: Uint8Array): Program + aggSigPuzzleAmount(publicKey: Uint8Array, message: Uint8Array): Program + aggSigParentAmount(publicKey: Uint8Array, message: Uint8Array): Program + aggSigParentPuzzle(publicKey: Uint8Array, message: Uint8Array): Program + aggSigUnsafe(publicKey: Uint8Array, message: Uint8Array): Program + aggSigMe(publicKey: Uint8Array, message: Uint8Array): Program + createCoin(puzzleHash: Uint8Array, amount: bigint, memos: Array): Program + reserveFee(fee: bigint): Program + createCoinAnnouncement(message: Uint8Array): Program + createPuzzleAnnouncement(message: Uint8Array): Program + assertCoinAnnouncement(announcementId: Uint8Array): Program + assertPuzzleAnnouncement(announcementId: Uint8Array): Program + assertConcurrentSpend(coinId: Uint8Array): Program + assertConcurrentPuzzle(puzzleHash: Uint8Array): Program + assertSecondsRelative(seconds: bigint): Program + assertSecondsAbsolute(seconds: bigint): Program + assertHeightRelative(height: number): Program + assertHeightAbsolute(height: number): Program + assertBeforeSecondsRelative(seconds: bigint): Program + assertBeforeSecondsAbsolute(seconds: bigint): Program + assertBeforeHeightRelative(height: number): Program + assertBeforeHeightAbsolute(height: number): Program + assertMyCoinId(coinId: Uint8Array): Program + assertMyParentId(parentId: Uint8Array): Program + assertMyPuzzleHash(puzzleHash: Uint8Array): Program + assertMyAmount(amount: bigint): Program + assertMyBirthSeconds(seconds: bigint): Program + assertMyBirthHeight(height: number): Program + assertEphemeral(): Program + sendMessage(mode: number, message: Uint8Array, data: Array): Program + receiveMessage(mode: number, message: Uint8Array, data: Array): Program + softfork(cost: bigint, value: Program): Program constructor() nil(): Program deserialize(value: Uint8Array): Program @@ -90,13 +125,7 @@ export declare class ClvmAllocator { treeHash(program: Program): Uint8Array run(puzzle: Program, solution: Program, maxCost: bigint, mempoolMode: boolean): Output curry(program: Program, args: Array): Program - newList(values: Array): Program - newPair(first: Program, rest: Program): Program - newAtom(value: Uint8Array): Program - newString(value: string): Program - newNumber(value: number): Program - newBigInt(value: bigint): Program - newBoolean(value: boolean): Program + pair(first: Program, rest: Program): Program alloc(value: ClvmValue): Program delegatedSpendForConditions(conditions: Array): Spend spendP2Standard(syntheticKey: Uint8Array, delegatedSpend: Spend): Spend diff --git a/napi/src/clvm.rs b/napi/src/clvm.rs index 02f39987..70e63cd5 100644 --- a/napi/src/clvm.rs +++ b/napi/src/clvm.rs @@ -5,7 +5,17 @@ use chia::{ protocol::Bytes32, puzzles::nft::{self, NFT_METADATA_UPDATER_PUZZLE_HASH}, }; -use chia_wallet_sdk::{self as sdk, Primitive, SpendContext}; +use chia_wallet_sdk::{ + self as sdk, AggSigAmount, AggSigMe, AggSigParent, AggSigParentAmount, AggSigParentPuzzle, + AggSigPuzzle, AggSigPuzzleAmount, AggSigUnsafe, AssertBeforeHeightAbsolute, + AssertBeforeHeightRelative, AssertBeforeSecondsAbsolute, AssertBeforeSecondsRelative, + AssertCoinAnnouncement, AssertConcurrentPuzzle, AssertConcurrentSpend, AssertEphemeral, + AssertHeightAbsolute, AssertHeightRelative, AssertMyAmount, AssertMyBirthHeight, + AssertMyBirthSeconds, AssertMyCoinId, AssertMyParentId, AssertMyPuzzleHash, + AssertPuzzleAnnouncement, AssertSecondsAbsolute, AssertSecondsRelative, CreateCoin, + CreateCoinAnnouncement, CreatePuzzleAnnouncement, Primitive, ReceiveMessage, Remark, + ReserveFee, SendMessage, Softfork, SpendContext, +}; use clvmr::{ run_program, serde::{node_from_bytes, node_from_bytes_backrefs}, @@ -14,6 +24,7 @@ use clvmr::{ use napi::bindgen_prelude::*; use crate::{ + clvm_value::{Allocate, ClvmValue}, traits::{FromJs, IntoJs, IntoRust}, Coin, CoinSpend, MintedNfts, Nft, NftMint, ParsedNft, Program, Spend, }; @@ -23,6 +34,166 @@ type Clvm = Reference; #[napi] pub struct ClvmAllocator(pub(crate) SpendContext); +macro_rules! conditions { + ( $( $condition:ident { $hint:literal $function:ident( $( $name:ident: $ty:ty $( => $remap:ty )? ),* ) }, )* ) => { + $( #[napi] + impl ClvmAllocator { + #[napi(ts_args_type = $hint)] + pub fn $function( &mut self, this: This, $( $name: $ty ),* ) -> Result { + $( let $name $( : $remap )? = FromJs::from_js($name)?; )* + let ptr = $condition::new( $( $name ),* ) + .to_clvm(&mut self.0.allocator) + .map_err(|error| Error::from_reason(error.to_string()))?; + + Ok(Program { ctx: this, ptr }) + } + } )* + }; +} + +conditions!( + Remark { + "value: Program" + remark(value: ClassInstance => NodePtr) + }, + AggSigParent { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_parent(public_key: Uint8Array, message: Uint8Array) + }, + AggSigPuzzle { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_puzzle(public_key: Uint8Array, message: Uint8Array) + }, + AggSigAmount { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_amount(public_key: Uint8Array, message: Uint8Array) + }, + AggSigPuzzleAmount { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_puzzle_amount(public_key: Uint8Array, message: Uint8Array) + }, + AggSigParentAmount { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_parent_amount(public_key: Uint8Array, message: Uint8Array) + }, + AggSigParentPuzzle { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_parent_puzzle(public_key: Uint8Array, message: Uint8Array) + }, + AggSigUnsafe { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_unsafe(public_key: Uint8Array, message: Uint8Array) + }, + AggSigMe { + "publicKey: Uint8Array, message: Uint8Array" + agg_sig_me(public_key: Uint8Array, message: Uint8Array) + }, + CreateCoin { + "puzzleHash: Uint8Array, amount: bigint, memos: Array" + create_coin(puzzle_hash: Uint8Array, amount: BigInt, memos: Vec) + }, + ReserveFee { + "fee: bigint" + reserve_fee(fee: BigInt) + }, + CreateCoinAnnouncement { + "message: Uint8Array" + create_coin_announcement(message: Uint8Array) + }, + CreatePuzzleAnnouncement { + "message: Uint8Array" + create_puzzle_announcement(message: Uint8Array) + }, + AssertCoinAnnouncement { + "announcementId: Uint8Array" + assert_coin_announcement(announcement_id: Uint8Array) + }, + AssertPuzzleAnnouncement { + "announcementId: Uint8Array" + assert_puzzle_announcement(announcement_id: Uint8Array) + }, + AssertConcurrentSpend { + "coinId: Uint8Array" + assert_concurrent_spend(coin_id: Uint8Array) + }, + AssertConcurrentPuzzle { + "puzzleHash: Uint8Array" + assert_concurrent_puzzle(puzzle_hash: Uint8Array) + }, + AssertSecondsRelative { + "seconds: bigint" + assert_seconds_relative(seconds: BigInt) + }, + AssertSecondsAbsolute { + "seconds: bigint" + assert_seconds_absolute(seconds: BigInt) + }, + AssertHeightRelative { + "height: number" + assert_height_relative(height: u32) + }, + AssertHeightAbsolute { + "height: number" + assert_height_absolute(height: u32) + }, + AssertBeforeSecondsRelative { + "seconds: bigint" + assert_before_seconds_relative(seconds: BigInt) + }, + AssertBeforeSecondsAbsolute { + "seconds: bigint" + assert_before_seconds_absolute(seconds: BigInt) + }, + AssertBeforeHeightRelative { + "height: number" + assert_before_height_relative(height: u32) + }, + AssertBeforeHeightAbsolute { + "height: number" + assert_before_height_absolute(height: u32) + }, + AssertMyCoinId { + "coinId: Uint8Array" + assert_my_coin_id(coin_id: Uint8Array) + }, + AssertMyParentId { + "parentId: Uint8Array" + assert_my_parent_id(parent_id: Uint8Array) + }, + AssertMyPuzzleHash { + "puzzleHash: Uint8Array" + assert_my_puzzle_hash(puzzle_hash: Uint8Array) + }, + AssertMyAmount { + "amount: bigint" + assert_my_amount(amount: BigInt) + }, + AssertMyBirthSeconds { + "seconds: bigint" + assert_my_birth_seconds(seconds: BigInt) + }, + AssertMyBirthHeight { + "height: number" + assert_my_birth_height(height: u32) + }, + AssertEphemeral { + "" + assert_ephemeral() + }, + SendMessage { + "mode: number, message: Uint8Array, data: Array" + send_message(mode: u8, message: Uint8Array, data: Vec> => Vec) + }, + ReceiveMessage { + "mode: number, message: Uint8Array, data: Array" + receive_message(mode: u8, message: Uint8Array, data: Vec> => Vec) + }, + Softfork { + "cost: bigint, value: Program" + softfork(cost: BigInt, value: ClassInstance => NodePtr) + }, +); + #[napi] impl ClvmAllocator { #[napi(constructor)] @@ -122,27 +293,8 @@ impl ClvmAllocator { .map(|ptr| Program { ctx: this, ptr }) } - #[napi(ts_args_type = "values: Array")] - pub fn new_list( - &mut self, - this: This, - values: Vec>, - ) -> Result { - let items: Vec = values.into_iter().map(|program| program.ptr).collect(); - let ptr = self - .0 - .alloc(&items) - .map_err(|error| Error::from_reason(error.to_string()))?; - Ok(Program { ctx: this, ptr }) - } - #[napi(ts_args_type = "first: Program, rest: Program")] - pub fn new_pair( - &mut self, - this: This, - first: &Program, - rest: &Program, - ) -> Result { + pub fn pair(&mut self, this: This, first: &Program, rest: &Program) -> Result { let ptr = self .0 .allocator @@ -151,57 +303,9 @@ impl ClvmAllocator { Ok(Program { ctx: this, ptr }) } - #[napi(ts_args_type = "value: Uint8Array")] - pub fn new_atom(&mut self, this: This, value: Uint8Array) -> Result { - let value: Vec = value.into_rust()?; - let ptr = self - .0 - .allocator - .new_atom(&value) - .map_err(|error| Error::from_reason(error.to_string()))?; - Ok(Program { ctx: this, ptr }) - } - - #[napi(ts_args_type = "value: string")] - pub fn new_string(&mut self, this: This, value: String) -> Result { - let ptr = self - .0 - .allocator - .new_atom(value.as_bytes()) - .map_err(|error| Error::from_reason(error.to_string()))?; - Ok(Program { ctx: this, ptr }) - } - - #[napi(ts_args_type = "value: number")] - pub fn new_number(&mut self, this: This, value: f64) -> Result { - let ptr = allocate_f64(&mut self.0.allocator, value)?; - Ok(Program { ctx: this, ptr }) - } - - #[napi(ts_args_type = "value: bigint")] - pub fn new_big_int(&mut self, this: This, value: BigInt) -> Result { - let value = value.into_rust()?; - let ptr = self - .0 - .allocator - .new_number(value) - .map_err(|error| Error::from_reason(error.to_string()))?; - Ok(Program { ctx: this, ptr }) - } - - #[napi(ts_args_type = "value: boolean")] - pub fn new_boolean(&mut self, this: This, value: bool) -> Result { - let ptr = self - .0 - .allocator - .new_small_number(u32::from(value)) - .map_err(|error| Error::from_reason(error.to_string()))?; - Ok(Program { ctx: this, ptr }) - } - #[napi(ts_args_type = "value: ClvmValue")] pub fn alloc(&mut self, this: This, value: ClvmValue) -> Result { - let ptr = allocate_any(&mut self.0.allocator, value)?; + let ptr = value.allocate(&mut self.0.allocator)?; Ok(Program { ctx: this, ptr }) } @@ -458,88 +562,3 @@ pub fn curry_tree_hash(tree_hash: Uint8Array, args: Vec) -> Result, Uint8Array, Array>; - -fn allocate_any(allocator: &mut clvmr::Allocator, value: ClvmValue) -> Result { - match value { - Either7::A(value) => allocate_f64(allocator, value), - Either7::B(value) => { - let value = value.into_rust()?; - allocator - .new_number(value) - .map_err(|error| Error::from_reason(error.to_string())) - } - Either7::C(value) => allocator - .new_atom(value.as_bytes()) - .map_err(|error| Error::from_reason(error.to_string())), - Either7::D(value) => { - let value = u32::from(value); - allocator - .new_small_number(value) - .map_err(|error| Error::from_reason(error.to_string())) - } - Either7::E(value) => Ok(value.ptr), - Either7::F(value) => { - let value: Vec = value.into_rust()?; - allocator - .new_atom(&value) - .map_err(|error| Error::from_reason(error.to_string())) - } - Either7::G(value) => { - let mut items = Vec::with_capacity(value.len() as usize); - - for i in 0..value.len() { - let Some(item) = value.get::(i)? else { - return Err(Error::from_reason(format!("Item at index {i} is missing"))); - }; - - items.push(allocate_any(allocator, item)?); - } - - items - .to_clvm(allocator) - .map_err(|error| Error::from_reason(error.to_string())) - } - } -} - -fn allocate_f64(allocator: &mut clvmr::Allocator, value: f64) -> Result { - if value.is_infinite() { - return Err(Error::from_reason("Value is infinite".to_string())); - } - - if value.is_nan() { - return Err(Error::from_reason("Value is NaN".to_string())); - } - - if value.fract() != 0.0 { - return Err(Error::from_reason( - "Value has a fractional part".to_string(), - )); - } - - if value > 9_007_199_254_740_991.0 { - return Err(Error::from_reason( - "Value is larger than MAX_SAFE_INTEGER".to_string(), - )); - } - - if value < -9_007_199_254_740_991.0 { - return Err(Error::from_reason( - "Value is smaller than MIN_SAFE_INTEGER".to_string(), - )); - } - - let value = value as i64; - - if (0..=67_108_863).contains(&value) { - allocator - .new_small_number(value as u32) - .map_err(|error| Error::from_reason(error.to_string())) - } else { - allocator - .new_number(value.into()) - .map_err(|error| Error::from_reason(error.to_string())) - } -} diff --git a/napi/src/clvm_value.rs b/napi/src/clvm_value.rs new file mode 100644 index 00000000..38f3752a --- /dev/null +++ b/napi/src/clvm_value.rs @@ -0,0 +1,126 @@ +use chia::clvm_traits::ToClvm; +use clvmr::{Allocator, NodePtr}; +use napi::bindgen_prelude::*; + +use crate::{traits::IntoRust, Program}; + +pub(crate) type ClvmValue = + Either7, Uint8Array, Array>; + +pub(crate) trait Allocate { + fn allocate(self, allocator: &mut Allocator) -> Result; +} + +impl Allocate for ClvmValue { + fn allocate(self, allocator: &mut Allocator) -> Result { + match self { + Either7::A(value) => value.allocate(allocator), + Either7::B(value) => value.allocate(allocator), + Either7::C(value) => value.allocate(allocator), + Either7::D(value) => value.allocate(allocator), + Either7::E(value) => value.allocate(allocator), + Either7::F(value) => value.allocate(allocator), + Either7::G(value) => value.allocate(allocator), + } + } +} + +impl Allocate for f64 { + fn allocate(self, allocator: &mut Allocator) -> Result { + if self.is_infinite() { + return Err(Error::from_reason("Value is infinite".to_string())); + } + + if self.is_nan() { + return Err(Error::from_reason("Value is NaN".to_string())); + } + + if self.fract() != 0.0 { + return Err(Error::from_reason( + "Value has a fractional part".to_string(), + )); + } + + if self > 9_007_199_254_740_991.0 { + return Err(Error::from_reason( + "Value is larger than MAX_SAFE_INTEGER".to_string(), + )); + } + + if self < -9_007_199_254_740_991.0 { + return Err(Error::from_reason( + "Value is smaller than MIN_SAFE_INTEGER".to_string(), + )); + } + + let value = self as i64; + + if (0..=67_108_863).contains(&value) { + allocator + .new_small_number(value as u32) + .map_err(|error| Error::from_reason(error.to_string())) + } else { + allocator + .new_number(value.into()) + .map_err(|error| Error::from_reason(error.to_string())) + } + } +} + +impl Allocate for BigInt { + fn allocate(self, allocator: &mut Allocator) -> Result { + let value = self.into_rust()?; + allocator + .new_number(value) + .map_err(|error| Error::from_reason(error.to_string())) + } +} + +impl Allocate for String { + fn allocate(self, allocator: &mut Allocator) -> Result { + allocator + .new_atom(self.as_bytes()) + .map_err(|error| Error::from_reason(error.to_string())) + } +} + +impl Allocate for bool { + fn allocate(self, allocator: &mut Allocator) -> Result { + allocator + .new_small_number(u32::from(self)) + .map_err(|error| Error::from_reason(error.to_string())) + } +} + +impl Allocate for Uint8Array { + fn allocate(self, allocator: &mut Allocator) -> Result { + let value: Vec = self.into_rust()?; + allocator + .new_atom(&value) + .map_err(|error| Error::from_reason(error.to_string())) + } +} + +impl Allocate for Array { + fn allocate(self, allocator: &mut Allocator) -> Result { + let mut items = Vec::with_capacity(self.len() as usize); + + for i in 0..self.len() { + let Some(item) = self.get::(i)? else { + return Err(Error::from_reason(format!("Item at index {i} is missing"))); + }; + + items.push(item.allocate(allocator)?); + } + + items + .to_clvm(allocator) + .map_err(|error| Error::from_reason(error.to_string())) + } +} + +impl Allocate for ClassInstance { + fn allocate(self, _allocator: &mut Allocator) -> Result { + Ok(self.ptr) + } +} diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 0175e6ac..cac4d54e 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -9,6 +9,7 @@ extern crate napi_derive; mod clvm; +mod clvm_value; mod coin; mod coin_spend; mod lineage_proof; diff --git a/napi/src/traits.rs b/napi/src/traits.rs index 68de1bcb..ec77447c 100644 --- a/napi/src/traits.rs +++ b/napi/src/traits.rs @@ -1,7 +1,8 @@ use chia::{ bls::PublicKey, - protocol::{BytesImpl, Program}, + protocol::{Bytes, BytesImpl, Program}, }; +use clvmr::NodePtr; use napi::bindgen_prelude::*; pub(crate) trait IntoJs { @@ -27,6 +28,48 @@ where } } +macro_rules! impl_primitive { + ( $( $ty:ty ),* ) => { + $( impl FromJs<$ty> for $ty { + fn from_js(value: $ty) -> Result { + Ok(value) + } + } + + impl IntoJs<$ty> for $ty { + fn into_js(self) -> Result { + Ok(self) + } + } )* + }; +} + +impl_primitive!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); + +impl FromJs> for Vec +where + T: FromJs, +{ + fn from_js(js_value: Vec) -> Result { + js_value.into_iter().map(FromJs::from_js).collect() + } +} + +impl IntoJs> for Vec +where + F: IntoJs, +{ + fn into_js(self) -> Result> { + self.into_iter().map(IntoJs::into_js).collect() + } +} + +impl FromJs> for NodePtr { + fn from_js(program: ClassInstance) -> Result { + Ok(program.ptr) + } +} + impl IntoJs for BytesImpl { fn into_js(self) -> Result { Ok(Uint8Array::new(self.to_vec())) @@ -69,6 +112,18 @@ impl FromJs for Vec { } } +impl IntoJs for Bytes { + fn into_js(self) -> Result { + Ok(Uint8Array::new(self.to_vec())) + } +} + +impl FromJs for Bytes { + fn from_js(js_value: Uint8Array) -> Result { + Ok(Bytes::from(js_value.to_vec())) + } +} + impl IntoJs for PublicKey { fn into_js(self) -> Result { Ok(Uint8Array::new(self.to_bytes().to_vec()))