diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 600b0e52ca..f6f29ec5df 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -715,7 +715,24 @@ jobs: cairo-compile cairo_programs/array_sum.cairo --no_debug_info --output cairo_programs/array_sum.json cd examples/wasm-demo wasm-pack build --target=web - + + wasm-demo-cairo1: + name: Build the wasm demo cairo1 + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install cairo-lang and deps + run: | + npm install -g wasm-pack + - name: Build wasm-demo-cairo1 + run: | + cd examples/wasm-demo-cairo1 + wasm-pack build --target=web + compare-factorial-outputs-all-layouts: name: Compare factorial outputs for all layouts needs: [ build-programs, build-release ] @@ -832,4 +849,3 @@ jobs: - name: Run comparison run: ./vm/src/tests/compare_all_pie_outputs.sh - diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f4001b5a7..0a9ebd97b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ * chore: bump `cairo-lang-` dependencies to 2.8.0 [#1833](https://github.com/lambdaclass/cairo-vm/pull/1833/files) * chore: update Rust required version to 1.80.0 +* chore: make cairo 1.0 compatible with wasm [#1830](https://github.com/lambdaclass/cairo-vm/pull/1830) + * fix: Added the following VM fixes: [#1820](https://github.com/lambdaclass/cairo-vm/pull/1820) * Fix zero segment location. * Fix has_zero_segment naming. diff --git a/Cargo.lock b/Cargo.lock index 03c0cb9c47..f1cd1be39a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -996,6 +996,7 @@ dependencies = [ "rstest", "serde_json", "thiserror", + "thiserror-no-std", ] [[package]] @@ -3619,6 +3620,19 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "wasm-demo-cairo1" +version = "1.0.1" +dependencies = [ + "cairo-lang-sierra", + "cairo-vm", + "cairo1-run", + "console_error_panic_hook", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-test", +] + [[package]] name = "web-sys" version = "0.3.69" diff --git a/Cargo.toml b/Cargo.toml index 64a4af3e09..dba693b30f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "vm", "hint_accountant", "examples/wasm-demo", + "examples/wasm-demo-cairo1", "cairo1-run", "cairo-vm-tracer", "examples/hyper_threading", @@ -25,6 +26,7 @@ keywords = ["starknet", "cairo", "vm", "wasm", "no_std"] [workspace.dependencies] cairo-vm = { path = "./vm", version = "1.0.1", default-features = false } +cairo1-run = { path = "./cairo1-run", version = "1.0.1", default-features = false } cairo-vm-tracer = { path = "./cairo-vm-tracer", version = "1.0.1", default-features = false } mimalloc = { version = "0.1.37", default-features = false } num-bigint = { version = "0.4", default-features = false, features = [ @@ -89,6 +91,9 @@ lto = "fat" # Tell `rustc` to optimize for small code size. opt-level = "s" +[profile.release.package.wasm-demo-cairo1] +opt-level = "s" + [profile.test.package.proptest] opt-level = 3 diff --git a/cairo1-run/Cargo.toml b/cairo1-run/Cargo.toml index 500d410d28..2a78766aae 100644 --- a/cairo1-run/Cargo.toml +++ b/cairo1-run/Cargo.toml @@ -9,7 +9,8 @@ keywords.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cairo-vm = { workspace = true, features = ["std", "cairo-1-hints", "clap"] } +cairo-vm = {workspace = true, features = ["cairo-1-hints", "clap"]} + serde_json = { workspace = true } cairo-lang-sierra-type-size = { version = "2.8.0", default-features = false } @@ -23,7 +24,8 @@ cairo-lang-utils.workspace = true cairo-lang-casm.workspace = true itertools = "0.11.0" clap = { version = "4.3.10", features = ["derive"] } -thiserror = { version = "1.0.40" } +thiserror = { version = "1.0.40", optional = true } +thiserror-no-std = { workspace = true } bincode.workspace = true assert_matches = "1.5.0" rstest = "0.17.0" @@ -34,3 +36,9 @@ num-bigint.workspace = true [features] default = ["with_mimalloc"] with_mimalloc = ["dep:mimalloc"] +cli = ["dep:thiserror", "cairo-vm/std"] + +[[bin]] +name = "cairo1-run" +path = "./src/main.rs" +required-features = ["cli"] diff --git a/cairo1-run/Makefile b/cairo1-run/Makefile index a00f9dfe3f..1ecba86e26 100644 --- a/cairo1-run/Makefile +++ b/cairo1-run/Makefile @@ -3,10 +3,10 @@ CAIRO_1_FOLDER=../cairo_programs/cairo-1-programs $(CAIRO_1_FOLDER)/%.trace: $(CAIRO_1_FOLDER)/%.cairo - cargo run --release $< --trace_file $@ --layout all_cairo + cargo run --release --features cli $< --trace_file $@ --layout all_cairo $(CAIRO_1_FOLDER)/%.memory: $(CAIRO_1_FOLDER)/%.cairo - cargo run --release $< --memory_file $@ --layout all_cairo + cargo run --release --features cli $< --memory_file $@ --layout all_cairo CAIRO_1_PROGRAMS=$(wildcard ../cairo_programs/cairo-1-programs/*.cairo) TRACES:=$(patsubst $(CAIRO_1_FOLDER)/%.cairo, $(CAIRO_1_FOLDER)/%.trace, $(CAIRO_1_PROGRAMS)) @@ -20,7 +20,7 @@ deps: run: $(TRACES) $(MEMORY) test: - cargo test + cargo test --features cli clean: rm -rf corelib diff --git a/cairo1-run/README.md b/cairo1-run/README.md index 9271d860bb..e41814d261 100644 --- a/cairo1-run/README.md +++ b/cairo1-run/README.md @@ -7,7 +7,7 @@ Once you are inside the `./cairo1-run` folder, use the CLI with the following co To install the required dependencies(cairo corelib) run ```bash -make deps +make deps ``` Now that you have the dependencies necessary to run the tests, you can run: @@ -16,18 +16,17 @@ Now that you have the dependencies necessary to run the tests, you can run: make test ``` -To execute a Cairo 1 program (either as Cairo 1 source file or Sierra) +To execute a Cairo 1 program (either as Cairo 1 source file or Sierra). Make sure the `cli` feature is active in order to use `cairo1-run` as a binary. ```bash -cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo +cargo run --features cli ../cairo_programs/cairo-1-programs/fibonacci.cairo ``` Arguments to generate the trace and memory files ```bash -cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo --trace_file ../cairo_programs/cairo-1-programs/fibonacci.trace --memory_file ../cairo_programs/cairo-1-programs/fibonacci.memory +cargo run --features cli ../cairo_programs/cairo-1-programs/fibonacci.cairo --trace_file ../cairo_programs/cairo-1-programs/fibonacci.trace --memory_file ../cairo_programs/cairo-1-programs/fibonacci.memory ``` - To pass arguments to `main` * Separate arguments with a whitespace inbetween @@ -37,14 +36,14 @@ Example: ```bash -cargo run ../cairo_programs/cairo-1-programs/with_input/array_input_sum.cairo --layout all_cairo --args '2 [1 2 3 4] 0 [9 8]' +cargo run --features cli ../cairo_programs/cairo-1-programs/with_input/array_input_sum.cairo --layout all_cairo --args '2 [1 2 3 4] 0 [9 8]' ``` To execute all the cairo 1 programs inside `../cairo_programs/cairo-1-programs/` and generate the corresponding trace and the memory files ```bash -make run +make run ``` ## CLI argument list @@ -86,7 +85,7 @@ Then run the compiled project's sierra file located at `project_name/target/proj Example: ```bash - cargo run path-to-project/target/project_name.sierra.json + cargo run --features cli path-to-project/target/project_name.sierra.json ``` # Known bugs & issues diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index bf654e9724..f851715797 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -37,6 +37,7 @@ use cairo_vm::{ hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, math_utils::signed_felt, serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams, ReferenceManager}, + stdlib::{cmp, collections::HashMap, iter::Peekable}, types::{ builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName, program::Program, relocatable::MaybeRelocatable, @@ -51,7 +52,6 @@ use cairo_vm::{ use itertools::{chain, Itertools}; use num_bigint::{BigInt, Sign}; use num_traits::{cast::ToPrimitive, Zero}; -use std::{collections::HashMap, iter::Peekable}; /// Representation of a cairo argument /// Can consist of a single Felt or an array of Felts @@ -142,7 +142,7 @@ pub fn cairo_run_program( let main_func = find_function(sierra_program, "::main")?; - let initial_gas = 9999999999999_usize; + let initial_gas = u32::MAX as usize; // Fetch return type data let return_type_id = match main_func.signature.ret_types.last() { @@ -1234,7 +1234,7 @@ fn serialize_output_inner<'a>( .expect("Missing return value") .get_relocatable() .expect("Box Pointer is not Relocatable"); - let type_size = type_sizes[&info.ty].try_into().expect("could not parse to usize"); + let type_size = type_sizes[&info.ty].try_into().expect("could not parse to usize"); let data = vm .get_continuous_range(ptr, type_size) .expect("Failed to extract value from nullable ptr"); @@ -1387,7 +1387,7 @@ fn serialize_output_inner<'a>( let mut max_variant_size = 0; for variant in &info.variants { let variant_size = type_sizes.get(variant).unwrap(); - max_variant_size = std::cmp::max(max_variant_size, *variant_size) + max_variant_size = cmp::max(max_variant_size, *variant_size) } for _ in 0..max_variant_size - type_sizes.get(variant_type_id).unwrap() { // Remove padding diff --git a/cairo1-run/src/error.rs b/cairo1-run/src/error.rs index 49f2c18cb9..f0ac6d7dcf 100644 --- a/cairo1-run/src/error.rs +++ b/cairo1-run/src/error.rs @@ -10,12 +10,16 @@ use cairo_vm::{ }, Felt252, }; +#[cfg(feature = "cli")] use thiserror::Error; +#[cfg(not(feature = "cli"))] +use thiserror_no_std::Error; #[derive(Debug, Error)] pub enum Error { #[error("Invalid arguments")] Cli(#[from] clap::Error), + #[cfg(feature = "cli")] #[error("Failed to interact with the file system")] IO(#[from] std::io::Error), #[error(transparent)] diff --git a/examples/wasm-demo-cairo1/Cargo.toml b/examples/wasm-demo-cairo1/Cargo.toml new file mode 100644 index 0000000000..648d63ceb7 --- /dev/null +++ b/examples/wasm-demo-cairo1/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "wasm-demo-cairo1" +description = "A demo using cairo-vm in a WASM environment" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme = "README.md" +keywords.workspace = true +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +serde_json.workspace = true +wasm-bindgen = "0.2.87" +cairo-lang-sierra = { workspace = true } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.6", optional = true } + +cairo-vm = { workspace = true } +cairo1-run = { workspace = true } + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" diff --git a/examples/wasm-demo-cairo1/README.md b/examples/wasm-demo-cairo1/README.md new file mode 100644 index 0000000000..5b3a466c53 --- /dev/null +++ b/examples/wasm-demo-cairo1/README.md @@ -0,0 +1,58 @@ +# Demo of `cairo-vm` on WebAssembly + +While cairo-vm is compatible with WebAssembly, it doesn't implement any bindings to it. +Instead, create a new WebAssembly crate with cairo-vm and cairo1-run as dependencies and implement the required functionality there. + +Since mimalloc is not automatically compilable to WebAssembly, the cairo-vm dependency should disable the default features, which will in turn disable mimalloc. + +A working example is provided in this repository. + +## Dependencies + +To compile and run the example you need: + +- an either Cairo 1 or Cairo 2 compiler +- the _wasm-pack_ crate +- some HTTP server (for example: the `live-server` npm module) + +> **Note** +> The first two dependencies can be installed via the repository's installation script (see ["Installation script"](../../README.md#installation-script)) + +## Building + +To build the example, first compile your Cairo 1 / 2 program: + +Cairo 1 + +```sh +../../cairo1/bin/cairo-compile -r ./bitwise.cairo bitwise.sierra +``` + +Cairo 2 + +```sh +../../cairo2/bin/cairo-compile -r ./bitwise.cairo bitwise.sierra +``` + +> It's important to use the `-r` flag. If not, the `main` function won't be recognized. + +And then the WebAssembly package: + +```sh +wasm-pack build --target=web +``` + +This will generate a javascript module that is directly loadable by the browser. + +## Running + +To run the example webpage, you need to run an HTTP server. +For example, using the _live-server_ npm module: + +```sh +# while in /examples/wasm-demo-cairo1 +npx live-server +``` + +> **Warning** +> Trying to run `index.html` directly (i.e. URL starts with `file://`) will result in a CORS error. diff --git a/examples/wasm-demo-cairo1/bitwise.cairo b/examples/wasm-demo-cairo1/bitwise.cairo new file mode 100644 index 0000000000..5b772d31c6 --- /dev/null +++ b/examples/wasm-demo-cairo1/bitwise.cairo @@ -0,0 +1,12 @@ +fn main() -> u128 { + let a = 1234_u128; + let b = 5678_u128; + + let c0 = a & b; + let c1 = a ^ b; + let c2 = a | b; + + let c3 = c0 + c1 + c2; + + c3 +} diff --git a/examples/wasm-demo-cairo1/bitwise.sierra b/examples/wasm-demo-cairo1/bitwise.sierra new file mode 100644 index 0000000000..e1131d67ec --- /dev/null +++ b/examples/wasm-demo-cairo1/bitwise.sierra @@ -0,0 +1,136 @@ +type u128 = u128; +type Bitwise = Bitwise; +type RangeCheck = RangeCheck; +type Tuple = Struct; +type felt252 = felt252; +type Array = Array; +type core::PanicResult::<(core::integer::u128,)> = Enum, Tuple, Array>; +type core::result::Result:: = Enum, u128, u128>; + +libfunc u128_const<1234> = u128_const<1234>; +libfunc u128_const<5678> = u128_const<5678>; +libfunc store_temp = store_temp; +libfunc dup = dup; +libfunc bitwise = bitwise; +libfunc drop = drop; +libfunc store_temp = store_temp; +libfunc function_call = function_call; +libfunc store_temp = store_temp; +libfunc enum_match> = enum_match>; +libfunc branch_align = branch_align; +libfunc struct_deconstruct> = struct_deconstruct>; +libfunc struct_construct> = struct_construct>; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp> = store_temp>; +libfunc enum_init, 1> = enum_init, 1>; +libfunc u128_overflowing_add = u128_overflowing_add; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc enum_init, 1> = enum_init, 1>; +libfunc felt252_const<39878429859757942499084499860145094553463> = felt252_const<39878429859757942499084499860145094553463>; +libfunc rename> = rename>; +libfunc store_temp = store_temp; +libfunc function_call::expect::> = function_call::expect::>; +libfunc enum_match> = enum_match>; +libfunc drop = drop; +libfunc array_new = array_new; +libfunc array_append = array_append; + +u128_const<1234>() -> ([2]); +u128_const<5678>() -> ([3]); +store_temp([2]) -> ([2]); +dup([2]) -> ([2], [8]); +store_temp([3]) -> ([3]); +dup([3]) -> ([3], [9]); +bitwise([1], [8], [9]) -> ([4], [5], [6], [7]); +drop([6]) -> (); +drop([7]) -> (); +dup([2]) -> ([2], [14]); +dup([3]) -> ([3], [15]); +bitwise([4], [14], [15]) -> ([10], [11], [12], [13]); +drop([11]) -> (); +drop([13]) -> (); +bitwise([10], [2], [3]) -> ([16], [17], [18], [19]); +drop([17]) -> (); +drop([18]) -> (); +store_temp([0]) -> ([22]); +store_temp([5]) -> ([23]); +store_temp([12]) -> ([24]); +function_call([22], [23], [24]) -> ([20], [21]); +store_temp([19]) -> ([19]); +store_temp([16]) -> ([16]); +enum_match>([21]) { fallthrough([25]) 45([26]) }; +branch_align() -> (); +struct_deconstruct>([25]) -> ([27]); +store_temp([20]) -> ([30]); +store_temp([27]) -> ([31]); +store_temp([19]) -> ([32]); +function_call([30], [31], [32]) -> ([28], [29]); +enum_match>([29]) { fallthrough([33]) 39([34]) }; +branch_align() -> (); +struct_deconstruct>([33]) -> ([35]); +struct_construct>([35]) -> ([36]); +enum_init, 0>([36]) -> ([37]); +store_temp([28]) -> ([38]); +store_temp([16]) -> ([39]); +store_temp>([37]) -> ([40]); +return([38], [39], [40]); +branch_align() -> (); +enum_init, 1>([34]) -> ([41]); +store_temp([28]) -> ([42]); +store_temp([16]) -> ([43]); +store_temp>([41]) -> ([44]); +return([42], [43], [44]); +branch_align() -> (); +drop([19]) -> (); +enum_init, 1>([26]) -> ([45]); +store_temp([20]) -> ([46]); +store_temp([16]) -> ([47]); +store_temp>([45]) -> ([48]); +return([46], [47], [48]); +u128_overflowing_add([0], [1], [2]) { fallthrough([3], [4]) 58([5], [6]) }; +branch_align() -> (); +enum_init, 0>([4]) -> ([7]); +store_temp([3]) -> ([8]); +store_temp>([7]) -> ([9]); +jump() { 62() }; +branch_align() -> (); +enum_init, 1>([6]) -> ([10]); +store_temp([5]) -> ([8]); +store_temp>([10]) -> ([9]); +felt252_const<39878429859757942499084499860145094553463>() -> ([11]); +rename>([9]) -> ([13]); +store_temp([11]) -> ([14]); +function_call::expect::>([13], [14]) -> ([12]); +enum_match>([12]) { fallthrough([15]) 74([16]) }; +branch_align() -> (); +struct_deconstruct>([15]) -> ([17]); +struct_construct>([17]) -> ([18]); +enum_init, 0>([18]) -> ([19]); +store_temp([8]) -> ([20]); +store_temp>([19]) -> ([21]); +return([20], [21]); +branch_align() -> (); +enum_init, 1>([16]) -> ([22]); +store_temp([8]) -> ([23]); +store_temp>([22]) -> ([24]); +return([23], [24]); +enum_match>([0]) { fallthrough([2]) 86([3]) }; +branch_align() -> (); +drop([1]) -> (); +struct_construct>([2]) -> ([4]); +enum_init, 0>([4]) -> ([5]); +store_temp>([5]) -> ([6]); +return([6]); +branch_align() -> (); +drop([3]) -> (); +array_new() -> ([7]); +array_append([7], [1]) -> ([8]); +enum_init, 1>([8]) -> ([9]); +store_temp>([9]) -> ([10]); +return([10]); + +bitwise::bitwise::main@0([0]: RangeCheck, [1]: Bitwise) -> (RangeCheck, Bitwise, core::PanicResult::<(core::integer::u128,)>); +core::integer::U128Add::add@52([0]: RangeCheck, [1]: u128, [2]: u128) -> (RangeCheck, core::PanicResult::<(core::integer::u128,)>); +core::result::ResultTraitImpl::::expect::@79([0]: core::result::Result::, [1]: felt252) -> (core::PanicResult::<(core::integer::u128,)>); diff --git a/examples/wasm-demo-cairo1/index.html b/examples/wasm-demo-cairo1/index.html new file mode 100644 index 0000000000..beb451b259 --- /dev/null +++ b/examples/wasm-demo-cairo1/index.html @@ -0,0 +1,28 @@ + + + + + + + + Cairo WebAssembly Demo + + + + + +

Result:

+ + diff --git a/examples/wasm-demo-cairo1/src/lib.rs b/examples/wasm-demo-cairo1/src/lib.rs new file mode 100644 index 0000000000..00ff0a2407 --- /dev/null +++ b/examples/wasm-demo-cairo1/src/lib.rs @@ -0,0 +1,58 @@ +mod utils; + +use cairo1_run::{cairo_run_program, Cairo1RunConfig}; +use cairo_lang_sierra::ProgramParser; +use cairo_vm::types::layout_name::LayoutName; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(msg: &str); +} + +#[wasm_bindgen(start)] +pub fn start() { + crate::utils::set_panic_hook(); +} + +// TODO: check why this is needed. Seems wasm-bindgen expects us to use +// `std::error::Error` even if it's not yet in `core` +macro_rules! wrap_error { + ($xp: expr) => { + $xp.map_err(|e| JsError::new(e.to_string().as_str())) + }; +} + +#[wasm_bindgen(js_name = runCairoProgram)] +pub fn run_cairo_program() -> Result { + let cairo_run_config = Cairo1RunConfig { + layout: LayoutName::all_cairo, + relocate_mem: true, + trace_enabled: true, + serialize_output: true, + ..Default::default() + }; + + // using cairo-lang 1.1.1 and wasm-demo-cairo1/bitwise.sierra + let sierra_program = match serde_json::from_slice(include_bytes!("../bitwise.sierra")) { + Ok(sierra) => sierra, + Err(_) => { + let program_str = include_str!("../bitwise.sierra"); + + let parser = ProgramParser::new(); + parser + .parse(program_str) + .map_err(|e| e.map_token(|t| t.to_string()))? + } + }; + + let (_, _, serielized_output_option) = + wrap_error!(cairo_run_program(&sierra_program, cairo_run_config))?; + + let output = serielized_output_option.unwrap(); + + log(&output); + + Ok(output) +} diff --git a/examples/wasm-demo-cairo1/src/utils.rs b/examples/wasm-demo-cairo1/src/utils.rs new file mode 100644 index 0000000000..b1d7929dc9 --- /dev/null +++ b/examples/wasm-demo-cairo1/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 07bcde08cd..0af2f7dac1 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -56,7 +56,7 @@ keccak = { workspace = true } hashbrown = { workspace = true } anyhow = { workspace = true } thiserror-no-std = { workspace = true } -starknet-types-core = { version = "0.1.2", default-features = false, features = ["serde", "curve", "num-traits", "hash"] } +starknet-types-core = { version = "0.1.2", default-features = false, features = [ "serde", "curve", "num-traits", "hash"] } rust_decimal = { version = "1.35.0", default-features = false } # only for std