diff --git a/Cargo.toml b/Cargo.toml index 3bc9322e..ba8f7a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "everscale-types" description = "A set of primitive types and utilities for the Everscale blockchain." authors = ["Ivan Kalinin "] repository = "https://github.com/broxus/everscale-types" -version = "0.1.0-rc.6" +version = "0.1.2" edition = "2021" rust-version = "1.77" include = ["src/**/*.rs", "benches/**/*.rs", "LICENSE-*", "README.md"] @@ -13,31 +13,61 @@ license = "MIT OR Apache-2.0" name = "boc" harness = false +[[bench]] +name = "mine" +harness = false + +[[bench]] +name = "dict_from_slice" +harness = false + [[bench]] name = "dict" harness = false +[[bench]] +name = "slice_uniform" +harness = false + [[bench]] name = "usage_cell" harness = false +# callgrind benchmarks + [[bench]] -name = "dict_from_slice" +name = "callgrind_boc" +harness = false + +[[bench]] +name = "callgrind_dict_from_slice" +harness = false + +[[bench]] +name = "callgrind_dict" +harness = false + +[[bench]] +name = "callgrind_slice_uniform" +harness = false + +[[bench]] +name = "callgrind_usage_cell" harness = false [workspace] members = ["proc"] [dependencies] -ahash = "0.8" +ahash = "0.8.11" anyhow = { version = "1.0", optional = true } base64 = { version = "0.22", optional = true } bitflags = "2.3" blake3 = { version = "1.5", optional = true } bytes = { version = "1.4", optional = true } -crc32c = "0.6" -ed25519-dalek = { version = "2.0", optional = true } -everscale-crypto = { version = "0.2", features = ["tl-proto"], optional = true } +crc32c = "0.6.8" +ed25519-dalek = { version = "2.1.1", optional = true } +everscale-crypto = { version = "0.3", features = ["tl-proto"], optional = true } hex = "0.4" num-bigint = { version = "0.4", optional = true } num-traits = { version = "0.2", optional = true } @@ -47,10 +77,11 @@ scc = { version = "2.1", optional = true } serde = { version = "1", features = ["derive"], optional = true } sha2 = "0.10" smallvec = { version = "1.9", features = ["union"] } -thiserror = "1.0" -tl-proto = { version = "0.4", optional = true } +thiserror = "2.0" +tl-proto = { version = "0.5.1", optional = true } +typeid = { version = "1.0", optional = true } -everscale-types-proc = { version = "=0.1.4", path = "proc" } +everscale-types-proc = { version = "=0.1.5", path = "proc" } [dev-dependencies] anyhow = "1.0" @@ -60,6 +91,8 @@ rand = "0.8" rand_xorshift = "0.3" serde = { version = "1", features = ["derive"] } serde_json = "1" +iai-callgrind = "0.14" +paste = "1.0.15" [features] default = ["base64", "serde", "models", "sync"] @@ -77,6 +110,7 @@ abi = [ "dep:num-bigint", "dep:num-traits", "dep:serde", + "dep:typeid", "models", ] venom = [] @@ -104,3 +138,6 @@ opt-level = 1 [package.metadata.docs.rs] features = ["base64", "serde", "models", "sync", "stats", "abi"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } diff --git a/benches/boc.rs b/benches/boc.rs index 12c99b55..5ee606dc 100644 --- a/benches/boc.rs +++ b/benches/boc.rs @@ -1,16 +1,7 @@ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use everscale_types::boc::Boc; -fn decode_base64(data: &str) -> Vec { - use base64::Engine as _; - base64::engine::general_purpose::STANDARD - .decode(data) - .unwrap() -} - -fn deserialize_boc(id: BenchmarkId, boc: &str, c: &mut Criterion) { - let boc = decode_base64(boc); - +fn deserialize_boc(id: BenchmarkId, boc: &'static [u8], c: &mut Criterion) { c.bench_with_input(id, &boc, |b, boc| { b.iter(|| { let result = Boc::decode(boc); @@ -19,8 +10,8 @@ fn deserialize_boc(id: BenchmarkId, boc: &str, c: &mut Criterion) { }); } -fn serialize_boc(id: BenchmarkId, boc: &str, c: &mut Criterion) { - let cell = Boc::decode_base64(boc).unwrap(); +fn serialize_boc(id: BenchmarkId, boc: &'static [u8], c: &mut Criterion) { + let cell = Boc::decode(boc).unwrap(); c.bench_with_input(id, &cell, |b, cell| { b.iter(|| { @@ -32,13 +23,14 @@ fn serialize_boc(id: BenchmarkId, boc: &str, c: &mut Criterion) { fn boc_group(c: &mut Criterion) { macro_rules! decl_boc_benches { - ($($name:literal => $boc:literal),*$(,)?) => { + ($($name:literal),*$(,)?) => { $({ let id = BenchmarkId::new( "deserialize_boc", format!("name={}", $name) ); - deserialize_boc(id, $boc, c); + let boc = include_bytes!(concat!("data/", $name)); + deserialize_boc(id, boc, c); });* $({ @@ -46,24 +38,23 @@ fn boc_group(c: &mut Criterion) { "serialize_boc", format!("name={}", $name) ); - serialize_boc(id, $boc, c); + let boc = include_bytes!(concat!("data/", $name)); + serialize_boc(id, boc, c); });* }; } decl_boc_benches![ - "external_message" => "te6ccgEBAwEA7gABRYgBGRoZkBXGlyf8MT+9+Aps6LyB9WVSLzZvhJSDPgmbHEIMAQHh8Nu9eCxecUj/vM96Y20RjiKgx6WoTw2DovvS/s9dA8fluaPCOfF9jDxVICPgt0F7bK5DLXQwAabrqb7Wnd+hgnWJpZrz4u8JX/jyyB6RENwoAPPEnVzvkFpHxK5gcHDrgAAAYW7VQB2Y8V2LAAAABGACAKMAAAAAAAAAAAAAAACy0F4AgBBMK6mc15szE1BZJlPsqtMkXmhvBh1UIAaIln9JSMkh+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARnFb2fy+DAM", - "internal_message_empty" => "te6ccgEBAQEAWwAAsUgBUkKKaORs1v/d2CpkdS1rueLjL5EbgaivG/SlIBcUZ5cAKkhRTRyNmt/7uwVMjqWtdzxcZfIjcDUV436UpALijPLQ7msoAAYUWGAAAD6o4PtmhMeK8nJA", - "internal_message_with_body" => "te6ccgEBBAEA7AABsWgBBMK6mc15szE1BZJlPsqtMkXmhvBh1UIAaIln9JSMkh8AKcyu6HDSN2uCXClQSdunN5ORKwsVegHnQNPiLAwT3wIQF0ZQIAYwZroAAD6ov3v2DMeK7AjAAQFLAAAADMAF47ShSRBdLiDscbrZ36xyWwI6GHiM/l4Mroth4ygz7HgCAaOABHg99SYML+GkoEJQXFyIG56xbLXbw9MCLDl9Vfnxmy7AAAAAAAAAAAAAAAAAAABD4AAAAAAAAAAAAAAAABMS0AAAAAAAAAAAAAACxOw48AAQAwAgAAAAAAAAAAAAAAAAAAAAAA==", - "internal_message_with_deploy" => "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", - - "masterchain_block" => "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", - "masterchain_key_block" => "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", - "shard_block_empty" => "te6ccgECFQEAA8YABBAR71WqAAAAKhIPBAEDiUoz9v1QIt7jANJzQctGcLhUBtlENxxQzrOhCdPHOG3NBnhHCR9gZ82qSA4rpOnxhd+UUvx2cd0qIp/or2qjVIdMEdoGQAMCAgABAgADACAKigR9nGzmP8ejgQmAaeGpILtImgOAypbmVkNQNDfHIrqBLkCBCq7mkZA8O8xyfg/H8b6+jQoQlwzoC/CDJFVYiCvbAhkCGQsFI1uQI6/iAAAAKgAAAAAAAAAAAAAAAAAB5VLiAAAAAGPO8fkAAB+IesjnAQF5N6EgBw0GANkAAAAAAAAAAP//////////gUTkn2uouLbDufYZvQyobpAAAfiHqqYoUBeTeiZvys7wWRmdRaSBABFtSHJLtUebY5JkfR9U8zNwQD6K27XEVqgu0ZfOLbulet2Ob4/4VHUpZOxPl41OygrENjWIAREAAAAAAAAAAFAIAhmvQAAAAAAAAAAAvJvRCgkAUUAAAfiHqqYoT//////////////////////////////////////////4AFFQAAH4h6uaTLFFBvP+w19A8NK2Cs5WDGzSYIQX5KGrGxqaYhT46ydf+CNbkCOv4gAAACoAAAAAAAAAAAAAAAAAAeVS4QAAAABjzvH3AAAfiHq5pM8BeTehIA4NDADZAAAAAAAAAAD//////////4FE5J9rqLi2w7n2GZ8/Q26QAAH4h6myBFAXk3ob8FPCVLkLD6y9lcoO8ynetg214BcScCBrGZo/plhvMgaNy3rMvVg9bFzOccDi6TzYu7vP/1QoXZMgfllXEJhZ6ChIAQEHXjyRaJqq3udYcfkUbeJy9W1X6kfy6ds2nZ+TE6GpVAIYKEgBAezENoQ2GF0kFS+yH3t/bxu8nx3ciCVREQcdb2/MOB8PAAECEbjkjftDuaygBBEQAA0AEO5rKAAIACWBROSfa6i4tsQKJyT7XUXFtgAIAqCbx6mHAAAAAIQBAeVS4gAAAAAAAAAAAAAAAAAAAAAAY87x+QAAH4h6yOcAAAAfiHrI5wFOOzUdAAU9DAF5N6EBeSBTxAAAACIAAAAAAAcXrhQTAJgAAB+IermkzwHlUuG24yvb5jr0/8FaryNIU8kEeLIrgfOXtf8NVff7x0YDD2tM3JxGB5w6r46bZN55+lYCdQkP3cFJXxLeR3ILq+NSAJgAAB+IeqpihQF5N6Jm/KzvBZGZ1FpIEAEW1Icku1R5tjkmR9H1TzM3BAPorbtcRWqC7Rl84tu6V63Y5vj/hUdSlk7E+XjU7KCsQ2NY", - "shard_block_with_messages" => "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", - - "masterchain_block_proof" => "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", - "shard_block_proof" => "te6ccgECDwEAAs8AAaXDBAAAAADwAAAAAAAAAAEndRPGh13esYv1+IiNBF8d0E4W9nAxxWox0/9dfbYfzbMNf7BAsJFC3f6OXhNcq+C1Z/dy9rq/xPVHxMsxSz9swGhTQAEJRgPGh13esYv1+IiNBF8d0E4W9nAxxWox0/9dfbYfzbMNfwAEAiQQEe9VqgAAACoDBAUGAqCbx6mHAAAAAIQBASd1EwAAAAAEAAAAAPAAAAAAAAAAYbUjQwAAE6ADeamAAAAToAN5qYHbMdUdAAMRZADINqwAyAO1xAAAAAcAAAAAAAAALgcIAhG45I37QDuaygQJCiqKBATDffPjKveSZSYvtkZDpIzT+6RfYUrlPaItUrZS8V7HQaP1v/aOVl4LiMM8qu+JUek2p0uQuIHV4AaCN5mrVOwAewB7CwwDiUoz9v0ZQGFiq3f3DnYsuIgizbBpIte2d8WchEzwPiE3cn+PRhRnl9e52rlFK8AWPE59DDMfnMn3HEPFdaM6B7NN0/8dQA0ODgCYAAAToANbJQQAyDasn3vQI1cAvX7TsH6cpLNG/Gcli4REHLB9gYLvAY1RwRThGBO5s7HGv5it3cNwmlhKe7UHYRvZ3j852Y8MYel2RwCYAAAToANqZ0EBJ3USM2ZQuOyJ2K6GEos4at5g1bubtGURZk8RVDYd+S8wz4JIJyFKVPraOO4qeN3eYmJwv1rg2zn+GEDcpFrl66En0AAlgEruBB2JoVR0AldwIOxNCqOACAANABAO5rKACGiMAQMEw33z4yr3kmUmL7ZGQ6SM0/ukX2FK5T2iLVK2UvFex1tvG3oZaJke0Zq49+C17SaNZRZFg/fTM2Liga2R2p8fAHsAAmiMAQNBo/W/9o5WXguIwzyq74lR6TanS5C4gdXgBoI3matU7NxzJDjVef2kcydRvYGpPaaQxMZu1+tYTs5aS6nT9+8bAHsAAgADACAAAQI=", + "external_message", + "internal_message_empty", + "internal_message_with_body", + "internal_message_with_deploy", + "masterchain_block", + "masterchain_key_block", + "shard_block_empty", + "shard_block_with_messages", + "masterchain_block_proof", + "shard_block_proof" ]; } diff --git a/benches/callgrind_boc.rs b/benches/callgrind_boc.rs new file mode 100644 index 00000000..c0114de9 --- /dev/null +++ b/benches/callgrind_boc.rs @@ -0,0 +1,74 @@ +use everscale_types::boc::Boc; +use everscale_types::cell::Cell; +use iai_callgrind::{library_benchmark, library_benchmark_group, main}; +use std::hint::black_box; + +#[macro_export] +macro_rules! decl_boc_benches { + ($( + $name:literal + ),* $(,)?) => { + // generate setup functions for raw bytes + $( + paste::paste! { + fn [<$name _setup>]() -> &'static [u8] { + include_bytes!(concat!("data/", $name)) + } + } + )* + + // generate setup functions for cells + $( + paste::paste! { + fn [<$name _setup_de>]() -> Cell { + let bytes = include_bytes!(concat!("data/", $name)); + Boc::decode(&bytes).unwrap() + } + } + )* + + // generate benchmark functions attributes for decode / encode + paste::paste! { + #[library_benchmark] + $( + #[bench::[<$name>](setup = [<$name _setup>])] + )* + fn deserialize_boc(input: &[u8]) { + let result = Boc::decode(input); + _ = black_box(result); + } + + #[library_benchmark] + $( + #[bench::[<$name>](setup = [<$name _setup_de>])] + )* + fn serialize_boc(input: Cell) { + let result = Boc::encode(&input); + _ = black_box(result); + std::mem::forget(input); + } + } + }; +} + +decl_boc_benches![ + "external_message", + "internal_message_empty", + "internal_message_with_body", + "internal_message_with_deploy", + "masterchain_block", + "masterchain_key_block", + "shard_block_empty", + "shard_block_with_messages", + "masterchain_block_proof", + "shard_block_proof" +]; + +library_benchmark_group!( + name = benches; + benchmarks = + deserialize_boc, + serialize_boc +); + +main!(library_benchmark_groups = benches); diff --git a/benches/callgrind_dict.rs b/benches/callgrind_dict.rs new file mode 100644 index 00000000..66e70084 --- /dev/null +++ b/benches/callgrind_dict.rs @@ -0,0 +1,35 @@ +use everscale_types::{cell::*, dict::*}; +use iai_callgrind::{library_benchmark, library_benchmark_group, main}; +use rand::distributions::{Distribution, Standard}; +use rand::{Rng, SeedableRng}; +use std::hint::black_box; + +fn build_dict(num_elements: usize) -> Dict +where + Standard: Distribution + Distribution, + K: Store + DictKey, + V: Store, +{ + let mut rng = rand_xorshift::XorShiftRng::from_seed([0u8; 16]); + + let mut result = Dict::::new(); + for _ in 0..num_elements { + let key = rng.gen::(); + let value = rng.gen::(); + result.set(key, value).unwrap(); + } + result +} + +#[library_benchmark] +#[bench::small(10)] +#[bench::medium(100)] +#[bench::large(1000)] +#[bench::xlarge(10000)] +fn bench_build_dict_u64_u64(num_elements: usize) -> Dict { + black_box(build_dict(num_elements)) +} + +library_benchmark_group!(name = build_dict; benchmarks = bench_build_dict_u64_u64); + +main!(library_benchmark_groups = build_dict); diff --git a/benches/callgrind_dict_from_slice.rs b/benches/callgrind_dict_from_slice.rs new file mode 100644 index 00000000..78243eed --- /dev/null +++ b/benches/callgrind_dict_from_slice.rs @@ -0,0 +1,63 @@ +use everscale_types::{cell::*, dict::*}; +use iai_callgrind::{library_benchmark, library_benchmark_group, main}; +use rand::distributions::{Distribution, Standard}; +use rand::{Rng, SeedableRng}; +use std::hint::black_box; + +fn build_dict_inserts(num_elements: usize) -> Dict +where + Standard: Distribution + Distribution, + K: Store + DictKey, + V: Store, +{ + let mut rng = rand_xorshift::XorShiftRng::from_seed([0u8; 16]); + + let mut result = Dict::::new(); + for _ in 0..num_elements { + let key = rng.gen::(); + let value = rng.gen::(); + result.add(key, value).unwrap(); + } + result +} + +fn build_dict_leaves(num_elements: usize) -> Dict +where + Standard: Distribution + Distribution, + K: Store + DictKey + Ord, + V: Store, +{ + let mut rng = rand_xorshift::XorShiftRng::from_seed([0u8; 16]); + + let mut values = (0..num_elements) + .map(|_| (rng.gen::(), rng.gen::())) + .collect::>(); + values.sort_by(|(l, _), (r, _)| l.cmp(r)); + + Dict::::try_from_sorted_slice(&values).unwrap() +} + +#[library_benchmark] +#[bench::small(10)] +#[bench::medium(100)] +#[bench::large(1000)] +#[bench::xlarge(10000)] +fn bench_build_dict_u64_u64_inserts(num_elements: usize) -> Dict { + black_box(build_dict_inserts(num_elements)) +} + +#[library_benchmark] +#[bench::small(10)] +#[bench::medium(100)] +#[bench::large(1000)] +#[bench::xlarge(10000)] +fn bench_build_dict_u64_u64_leaves(num_elements: usize) -> Dict { + black_box(build_dict_leaves(num_elements)) +} + +library_benchmark_group!( + name = build_dict; + benchmarks = bench_build_dict_u64_u64_inserts, bench_build_dict_u64_u64_leaves +); + +main!(library_benchmark_groups = build_dict); diff --git a/benches/callgrind_slice_uniform.rs b/benches/callgrind_slice_uniform.rs new file mode 100644 index 00000000..31854f77 --- /dev/null +++ b/benches/callgrind_slice_uniform.rs @@ -0,0 +1,24 @@ +use everscale_types::prelude::*; +use iai_callgrind::{library_benchmark, library_benchmark_group, main}; +use std::hint::black_box; + +#[library_benchmark] +#[bench::small(2)] +#[bench::medium(4)] +#[bench::large(8)] +#[bench::xlarge(10)] +fn test(bits: u32) { + let mut builder = CellBuilder::new(); + builder.store_zeros(2u16.pow(bits) - 1u16).unwrap(); + let cell = builder.build().unwrap(); + + let slice = cell.as_slice().unwrap(); + black_box(slice.test_uniform()); +} + +library_benchmark_group!( + name = test_uniform; + benchmarks = test +); + +main!(library_benchmark_groups = test_uniform); diff --git a/benches/callgrind_usage_cell.rs b/benches/callgrind_usage_cell.rs new file mode 100644 index 00000000..c7211bdd --- /dev/null +++ b/benches/callgrind_usage_cell.rs @@ -0,0 +1,92 @@ +use everscale_types::cell::RefsIter; +use everscale_types::prelude::*; +use iai_callgrind::{library_benchmark, library_benchmark_group, main}; +use std::collections::HashSet; +use std::hint::black_box; + +const BOC: &str = "te6ccgECCAEAAWQAAnPP9noJKCEBL3oZerOiIcNghuL96V3wIcuYOWQdvNC+2fqCEIJDQAAAAAAAAAAAAAAAAZa8xB6QABNAAgEAUO3QlUyMI4dEepUMw3Ou6oSqq8+1lyHkjOGFK6DAn6TXAAAAAAAAAAABFP8A9KQT9LzyyAsDAgEgBwQC5vJx1wEBwADyeoMI1xjtRNCDB9cB1ws/yPgozxYjzxbJ+QADcdcBAcMAmoMH1wFRE7ry4GTegEDXAYAg1wGAINcBVBZ1+RDyqPgju/J5Zr74I4EHCKCBA+ioUiC8sfJ0AiCCEEzuZGy64w8ByMv/yz/J7VQGBQA+ghAWnj4Ruo4R+AACkyDXSpd41wHUAvsA6NGTMvI84gCYMALXTND6QIMG1wFx1wF41wHXTPgAcIAQBKoCFLHIywVQBc8WUAP6AstpItAhzzEh10mghAm5mDNwAcsAWM8WlzBxAcsAEsziyQH7AAAE0jA="; + +#[library_benchmark] +fn traverse_cell_ordinary() { + let cell = Boc::decode_base64(BOC).unwrap(); + + let mut visitor = Visitor::new(); + black_box(visitor.add_cell(cell.as_ref())); +} + +#[library_benchmark] +fn traverse_cell_storage_cell() { + let cell = Boc::decode_base64(BOC).unwrap(); + let usage_tree = UsageTree::new(UsageTreeMode::OnDataAccess); + let cell = usage_tree.track(&cell); + + let mut visitor = Visitor::new(); + black_box(visitor.add_cell(cell.as_ref())); +} + +#[library_benchmark] +fn traverse_cell_storage_cell_with_capacity() { + let cell = Boc::decode_base64(BOC).unwrap(); + let usage_tree = UsageTree::with_mode_and_capacity(UsageTreeMode::OnDataAccess, 100); + let cell = usage_tree.track(&cell); + + let mut visitor = Visitor::new(); + black_box(visitor.add_cell(cell.as_ref())); +} + +struct Visitor<'a> { + visited: ahash::HashSet<&'a HashBytes>, + stack: Vec>, +} + +impl<'a> Visitor<'a> { + fn new() -> Self { + Self { + visited: HashSet::with_hasher(ahash::RandomState::with_seed(0)), + stack: Vec::new(), + } + } + + fn add_cell(&mut self, cell: &'a DynCell) -> bool { + if !self.visited.insert(cell.repr_hash()) { + return true; + } + + self.stack.clear(); + self.stack.push(cell.references()); + self.reduce_stack() + } + + fn reduce_stack(&mut self) -> bool { + 'outer: while let Some(item) = self.stack.last_mut() { + for cell in item.by_ref() { + if !self.visited.insert(cell.repr_hash()) { + continue; + } + + let mut slice = cell.as_slice().unwrap(); + slice.load_bit().ok(); + slice.load_u32().ok(); + slice.load_small_uint(5).ok(); + slice.load_reference().ok(); + + let next = cell.references(); + if next.peek().is_some() { + self.stack.push(next); + continue 'outer; + } + } + + self.stack.pop(); + } + + true + } +} + +library_benchmark_group!( + name = traverse_cell; + benchmarks = traverse_cell_ordinary, traverse_cell_storage_cell, traverse_cell_storage_cell_with_capacity +); + +main!(library_benchmark_groups = traverse_cell); diff --git a/benches/data/external_message b/benches/data/external_message new file mode 100644 index 00000000..0117afd0 Binary files /dev/null and b/benches/data/external_message differ diff --git a/benches/data/internal_message_empty b/benches/data/internal_message_empty new file mode 100644 index 00000000..1842f27c Binary files /dev/null and b/benches/data/internal_message_empty differ diff --git a/benches/data/internal_message_with_body b/benches/data/internal_message_with_body new file mode 100644 index 00000000..db1380e7 Binary files /dev/null and b/benches/data/internal_message_with_body differ diff --git a/benches/data/internal_message_with_deploy b/benches/data/internal_message_with_deploy new file mode 100644 index 00000000..1e4299f6 Binary files /dev/null and b/benches/data/internal_message_with_deploy differ diff --git a/benches/data/masterchain_block b/benches/data/masterchain_block new file mode 100644 index 00000000..623c9ec4 Binary files /dev/null and b/benches/data/masterchain_block differ diff --git a/benches/data/masterchain_block_proof b/benches/data/masterchain_block_proof new file mode 100644 index 00000000..44b4d4fc Binary files /dev/null and b/benches/data/masterchain_block_proof differ diff --git a/benches/data/masterchain_key_block b/benches/data/masterchain_key_block new file mode 100644 index 00000000..863f7fb6 Binary files /dev/null and b/benches/data/masterchain_key_block differ diff --git a/benches/data/shard_block_empty b/benches/data/shard_block_empty new file mode 100644 index 00000000..55b411d5 Binary files /dev/null and b/benches/data/shard_block_empty differ diff --git a/benches/data/shard_block_proof b/benches/data/shard_block_proof new file mode 100644 index 00000000..24bdd287 Binary files /dev/null and b/benches/data/shard_block_proof differ diff --git a/benches/data/shard_block_with_messages b/benches/data/shard_block_with_messages new file mode 100644 index 00000000..0c3fb7c4 Binary files /dev/null and b/benches/data/shard_block_with_messages differ diff --git a/benches/mine.rs b/benches/mine.rs new file mode 100644 index 00000000..6c104239 --- /dev/null +++ b/benches/mine.rs @@ -0,0 +1,213 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use everscale_types::boc::Boc; +use everscale_types::cell::{Cell, CellBuilder, CellFamily, DynCell, HashBytes, StaticCell, Store}; +use everscale_types::dict::{Dict, RawDict}; +use everscale_types::models::{StateInit, StdAddr}; + +pub const MAX_NONCE: u64 = 300; + +#[derive(Debug)] +pub struct MinedData { + pub nonce: u64, + pub hash: HashBytes, + pub data: Vec, +} + +fn do_mine(code: &Cell, factory_addr: &StdAddr, recipient: &StdAddr, reward: u128) -> MinedData { + const KEY_ZERO: &DynCell = + &unsafe { StaticCell::new(&[0, 0, 0, 0, 0, 0, 0, 0, 0x80], 64, &[0u8; 32]) }; + const KEY_ONE: &DynCell = + &unsafe { StaticCell::new(&[0, 0, 0, 0, 0, 0, 0, 1, 0x80], 64, &[0u8; 32]) }; + const KEY_TWO: &DynCell = + &unsafe { StaticCell::new(&[0, 0, 0, 0, 0, 0, 0, 2, 0x80], 64, &[0u8; 32]) }; + + let cx = Cell::empty_context(); + + let target_bits = factory_addr.address.0[0] >> 4; + + let mut data = RawDict::<64>::new(); + + data.set_ext(KEY_ZERO.as_slice_allow_pruned(), &HashBytes::ZERO, cx) + .unwrap(); + + let airdrop_data_child = CellBuilder::build_from_ext((recipient, reward), cx).unwrap(); + let airdrop_data = CellBuilder::build_from_ext((factory_addr, airdrop_data_child), cx).unwrap(); + + data.set_ext(KEY_TWO.as_slice_allow_pruned(), &airdrop_data, cx) + .unwrap(); + + let nonce_key = KEY_ONE.as_slice_allow_pruned(); + + let mut nonce = 0; + loop { + let mut builder = CellBuilder::new(); + builder.store_zeros(192).unwrap(); + builder.store_u64(nonce).unwrap(); + data.set_ext(nonce_key, &builder.as_data_slice(), cx) + .unwrap(); + + let partial_state_init = CellBuilder::build_from_ext( + StateInit { + split_depth: None, + special: None, + code: Some(code.clone()), + data: Some(CellBuilder::build_from_ext(&data, cx).unwrap()), + libraries: Dict::new(), + }, + cx, + ) + .unwrap(); + + let address = partial_state_init.repr_hash(); + if address.0[0] >> 4 == target_bits || nonce >= MAX_NONCE { + let mut builder = CellBuilder::new(); + + let child = CellBuilder::build_from_ext((recipient, reward), cx).unwrap(); + + // uint256 nonce + builder.store_zeros(192).unwrap(); + builder.store_u64(nonce).unwrap(); + + // addr + factory_addr.store_into(&mut builder, cx).unwrap(); + builder.store_reference(child).unwrap(); + + let full_airdrop_data = builder.build_ext(cx).unwrap(); + + let hash = *full_airdrop_data.repr_hash(); + let data = Boc::encode(&full_airdrop_data); + + return MinedData { nonce, hash, data }; + } + + nonce += 1; + } +} + +fn mine_address(c: &mut Criterion) { + let inputs = [ + ( + "0:cf9b339921e0b981f4c0bc3053c4e386f14cf97127ffa297fd93d7de6a561ffa", + "0:cccc221b25626349429bc14669c6c072c1e238c6fe5d3ea02de707860a8c6d21", + 120000000, + 0, + ), + ( + "0:b84ee317b5ddb05002a52a0ffe6a524ccb4c87f9cf35b9a98a1ac623de70f113", + "0:bbbb83ba6af3bca1a78bf09271b163bbd54736272768c9544faf1efd1049dba6", + 110000000, + 3, + ), + ( + "0:da612c822390fdc8b62b14a432d401a73b3247dec38e3747fb9cd665ca4df5bf", + "0:ddddfdd246302e87bad55712acc50d4d72e55f5636a52741f6a188939e2fe518", + 210000000, + 4, + ), + ( + "0:7973aac2a0f1f1048aa4364ab545a1d1c85839f7dac7f16bec6cd4d9a252f92b", + "0:77775d17834917c7a5d66a59d3dedd2f993d0059d1824c7272854fc808acc574", + 170000000, + 5, + ), + ( + "0:b27e8a967935bcb17c1cf8afbaf09d317c1553926c9c772fd53240c0933c1043", + "0:bbbb83ba6af3bca1a78bf09271b163bbd54736272768c9544faf1efd1049dba6", + 150000000, + 7, + ), + ( + "0:b3de1d08c26fe6e35bddc3ac4efc573c33d639fa69195cad243e54e68f2dbdcd", + "0:bbbb83ba6af3bca1a78bf09271b163bbd54736272768c9544faf1efd1049dba6", + 220000000, + 9, + ), + ( + "0:12c9c61c113f292d19d2e7b29c578c0a6e0efaf52fb64d2582aa962b4c4fa908", + "0:1111160d461226ad45bacafbdf42bd76e33643547c41e2e42bcae19b42b90122", + 100000000, + 10, + ), + ( + "0:3d7c3041562f788f00381ddf5a61631b37a7ec13127f89b428ca713f547bb4ad", + "0:3334dd1d2f96f3907f7332d6974c383f51500130ce1bc00480020bbb52b8dfb3", + 160000000, + 14, + ), + ( + "0:387d7b2854430a64560179fcbdf9d3ba0950d6aaf481bd3664630d6211a7a23e", + "0:3334dd1d2f96f3907f7332d6974c383f51500130ce1bc00480020bbb52b8dfb3", + 180000000, + 15, + ), + ( + "0:962a78c7dfa5605327730fe3110eb3caed1310d6715de59ae6d4e6907c0ef2be", + "0:9999be620c23631cf75b16c53201e3bad9528c17118e9d124b644fb649bfdbad", + 130000000, + 18, + ), + ( + "0:8e63172ad2163783e195909a5e81aff8004dbf4357ad98400a4f84d9576d3701", + "0:8888b8c017f3afa3c16b394959fed181153edb7253c827c6ffd68ddb88560c8e", + 20000000, + 24, + ), + ( + "0:21e6c0a5a7a9f287d974601e594262a963f2df755de0714afbed3412698a03f6", + "0:2222d76864be64b9b872df63a311b1217d1cba3fe687cd107fd29b9b7fe01570", + 190000000, + 28, + ), + ( + "0:81de3632ee719ff174a0fe1318c28cbb0c80a8903a112fe4b864a193fbfa2584", + "0:8888b8c017f3afa3c16b394959fed181153edb7253c827c6ffd68ddb88560c8e", + 250000000, + 30, + ), + ( + "0:2f8737396a897945a082390fbb476a3151f2881dbd1cf6180b05fd000bfb205a", + "0:2222d76864be64b9b872df63a311b1217d1cba3fe687cd107fd29b9b7fe01570", + 140000000, + 37, + ), + ( + "0:49d5682a789d0d7820a9a9657df0280c87597e312344cdda08c51dd46996c00a", + "0:44442bf5dd7b2bd3653537e116a255baff32cfbc7af9f10128f2897eba3efd0b", + 240000000, + 46, + ), + ]; + + for (wallet_addr, factory_addr, tokens, nonce) in inputs { + let wallet_addr = wallet_addr.parse::().unwrap(); + let factory_addr = factory_addr.parse::().unwrap(); + + let mut group = c.benchmark_group("mine"); + group.bench_with_input(BenchmarkId::from_parameter(nonce), &nonce, move |b, _| { + b.iter(|| { + thread_local! { + static CODE_CELL: std::cell::UnsafeCell> = const { + std::cell::UnsafeCell::new(None) + }; + } + + CODE_CELL.with(|code_cell| { + let code_cell = { + let slot = unsafe { &mut *code_cell.get() }; + &*slot.get_or_insert_with(|| { + Boc::decode_base64("te6ccgECDgEAAd0ABCSK7VMg4wMgwP/jAiDA/uMC8gsBAgMMAhD0pCD0vfLATgwEA1LtRNDXScMB+GYi0NMD+kAw+GmpOADcIccA4wIh1w0f8rwh4wMB2zzyPAUFBwLA7UTQ10nDAfhmjQhgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE+Gkh2zzTAAGOH4MI1xgg+CjIzs7J+QAB0wABlNP/AwGTAvhC4vkQ8qiV0wAB8nri0z8BCQYAFHNvbCAwLjcxLjAACvhG8uBMAU74QyG58rQg+COBA+iogggbd0CgufK0+GPTHwH4I7zyudMfAds88jwHARQgghBCHlYVuuMCCAPEMPhCbuMA+EbycyGT1NHQ3vpA03/RW/hL0PpA1NHQ+kDTf9FbIPpCbxPXC//DAPhJWMcFsPLgZNs8cPsC+Ev4SvhJcMjPhYDKAM+EQM6CECeY5YTPC47L/8zJgwb7ANs88gAJCgsCdO1E0NdJwgGOr3DtRND0BXEhgED0DpPXC/+RcOJyIoBA9A+OgYjf+Gv4aoBA9A7yvdcL//hicPhj4w0MDQAKggnJw4AAKvhL+Er4Q/hCyMv/yz/Pg8v/zMntVAAAACztRNDT/9M/0wAx0//U0fhr+Gr4Y/hi").unwrap() + }) + }; + + let res = do_mine(code_cell, &factory_addr, &wallet_addr, tokens); + assert_eq!(res.nonce, nonce); + criterion::black_box( res); + }) + }); + }); + group.finish(); + } +} + +criterion_group!(mine, mine_address); +criterion_main!(mine); diff --git a/benches/slice_uniform.rs b/benches/slice_uniform.rs new file mode 100644 index 00000000..b178da0b --- /dev/null +++ b/benches/slice_uniform.rs @@ -0,0 +1,28 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use everscale_types::prelude::*; + +fn test_uniform(c: &mut Criterion) { + let cells = (0..=32) + .chain([ + 40, 60, 64, 80, 96, 127, 128, 160, 196, 200, 255, 256, 300, 400, 500, 600, 700, 800, + 900, 1000, 1023, + ]) + .map(|bits| { + let mut builder = CellBuilder::new(); + builder.store_zeros(bits).unwrap(); + builder.build().unwrap() + }) + .collect::>(); + + for cell in cells { + let slice = cell.as_slice().unwrap(); + c.bench_with_input( + BenchmarkId::new("test slice uniform", slice.size_bits()), + &slice, + |b, slice| b.iter(|| black_box(slice.test_uniform())), + ); + } +} + +criterion_group!(benches, test_uniform); +criterion_main!(benches); diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 7b2cd142..7c551449 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -13,11 +13,18 @@ libfuzzer-sys = "0.4" [dependencies.everscale-types] path = ".." +features = ["base64"] # Prevent this from interfering with workspaces [workspace] members = ["."] +[[bin]] +name = "base64_addr" +path = "fuzz_targets/base64_addr.rs" +test = false +doc = false + [[bin]] name = "boc_decode" path = "fuzz_targets/boc_decode.rs" diff --git a/fuzz/fuzz_targets/base64_addr.rs b/fuzz/fuzz_targets/base64_addr.rs new file mode 100644 index 00000000..e90f8483 --- /dev/null +++ b/fuzz/fuzz_targets/base64_addr.rs @@ -0,0 +1,14 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use everscale_types::models::{StdAddr, StdAddrFormat}; +use libfuzzer_sys::Corpus; + +fuzz_target!(|data: &[u8]| -> Corpus { + if let Ok(s) = std::str::from_utf8(data) { + if StdAddr::from_str_ext(s, StdAddrFormat::any()).is_ok() { + return Corpus::Keep; + } + } + Corpus::Reject +}); diff --git a/proc/Cargo.toml b/proc/Cargo.toml index e6212632..b994a69c 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -3,7 +3,7 @@ name = "everscale-types-proc" description = "Proc-macro helpers for everscale-types" authors = ["Ivan Kalinin "] repository = "https://github.com/broxus/everscale-types" -version = "0.1.4" +version = "0.1.5" edition = "2021" include = ["src/**/*.rs", "../LICENSE-*", "../README.md"] license = "MIT OR Apache-2.0" @@ -14,4 +14,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = { version = "2.0", features = ["visit"] } +syn = { version = "2.0.87", features = ["visit", "full"] } diff --git a/proc/src/derive_load.rs b/proc/src/derive_load.rs index 066b0d3d..6109e32c 100644 --- a/proc/src/derive_load.rs +++ b/proc/src/derive_load.rs @@ -70,11 +70,33 @@ fn build_struct( style: ast::Style, fields: &[ast::Field<'_>], ) -> TokenStream { - let condition = container.attrs.tlb_tag.and_then(load_tag_op); + let mut tag_version = None; + let tags = match &container.attrs.tlb_tag { + attr::ContainerTag::None => None, + attr::ContainerTag::Single(tag) => load_tag(*tag), + attr::ContainerTag::Multiple(tags) => load_tags_versioned(tags).map(|load_tags| { + let ident = quote::format_ident!("__tag_version"); + let res = quote! { let #ident = #load_tags; }; + tag_version = Some(ident); + res + }), + }; let members = fields.iter().map(|field| { let ident = &field.member; - let op = load_op(lifetime_def, field.ty); + let ty = field.ty; + let mut op = load_op(lifetime_def, ty); + + if let (Some(since), Some(tag_version)) = (field.attrs.since_tag, &tag_version) { + op = quote! { + if #tag_version >= #since { + #op + } else { + <#ty as Default>::default() + } + }; + } + quote! { #ident: #op } @@ -102,15 +124,75 @@ fn build_struct( }; quote! { - #condition + #tags #result } } -fn load_tag_op(tag: attr::TlbTag) -> Option { +fn load_tag(tag: attr::TlbTag) -> Option { + let (op, value) = load_tag_op_value(tag)?; + + Some(quote! { + match #op { + ::core::result::Result::Ok(#value) => {}, + ::core::result::Result::Ok(_) => return ::core::result::Result::Err(::everscale_types::error::Error::InvalidTag), + ::core::result::Result::Err(e) => return ::core::result::Result::Err(e), + } + }) +} + +fn load_tags_versioned(tags: &[attr::TlbTag]) -> Option { + let mut iter = tags.iter(); + + let first_tag = iter.next()?; + let (op, first_value) = load_tag_op_value(*first_tag)?; + + let mut values = Vec::with_capacity(tags.len()); + values.push(first_value); + + for tag in iter { + values.push(match tag.bits { + 0 => continue, + 1 => { + let value = tag.value != 0; + quote!(#value) + } + 2..=8 => { + let value = tag.value as u8; + quote!(#value) + } + 16 => { + let value = tag.value as u16; + quote!(#value) + } + 32 => { + let value = tag.value; + quote!(#value) + } + _ => { + let value = tag.value as u64; + quote!(#value) + } + }); + } + + let values = values.into_iter().enumerate().map(|(i, value)| { + quote! { ::core::result::Result::Ok(#value) => #i } + }); + + Some(quote! { + match #op { + #(#values),*, + ::core::result::Result::Ok(_) => return ::core::result::Result::Err(::everscale_types::error::Error::InvalidTag), + ::core::result::Result::Err(e) => return ::core::result::Result::Err(e), + } + }) +} + +fn load_tag_op_value(tag: attr::TlbTag) -> Option<(TokenStream, TokenStream)> { let bits = tag.bits as u16; - let (op, value) = match bits { + Some(match bits { 0 => return None, 1 => { let value = tag.value != 0; @@ -136,14 +218,6 @@ fn load_tag_op(tag: attr::TlbTag) -> Option { let value = tag.value as u64; (quote!(__slice.load_uint(#bits)), quote!(#value)) } - }; - - Some(quote! { - match #op { - ::core::result::Result::Ok(#value) => {}, - ::core::result::Result::Ok(_) => return ::core::result::Result::Err(::everscale_types::error::Error::InvalidTag), - ::core::result::Result::Err(e) => return ::core::result::Result::Err(e), - } }) } diff --git a/proc/src/derive_store.rs b/proc/src/derive_store.rs index 7190eb46..c0a4a61a 100644 --- a/proc/src/derive_store.rs +++ b/proc/src/derive_store.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use proc_macro2::TokenStream; use quote::quote; @@ -32,7 +34,7 @@ pub fn impl_derive(input: syn::DeriveInput) -> Result ::core::result::Result<(), ::everscale_types::error::Error> { #body } @@ -51,17 +53,40 @@ fn build_struct( style: ast::Style, fields: &[ast::Field<'_>], ) -> TokenStream { - let store_tag = container.attrs.tlb_tag.and_then(store_tag_op); + let mut tag_version = None; + let (compute_tag_version, store_tag) = match &container.attrs.tlb_tag { + attr::ContainerTag::None => (None, None), + attr::ContainerTag::Single(tag) => (None, store_tag(*tag)), + attr::ContainerTag::Multiple(tags) => store_tags_versioned(tags, fields, &mut tag_version), + }; let fields_len = fields.len(); let members = fields.iter().enumerate().map(|(i, field)| { let ident = &field.member; let field_ident = quote!(self.#ident); let op = store_op(&field_ident, field.ty); - if i + 1 == fields_len { - op - } else { - into_ok(op) + + let is_last = i + 1 == fields_len; + match (field.attrs.since_tag, &tag_version) { + (Some(since), Some(tag_version)) if is_last => { + quote! { + if #tag_version >= #since { + #op + } else { + Ok(()) + } + } + } + (Some(since), Some(tag_version)) => { + let op = into_ok(op); + quote! { + if #tag_version >= #since { + #op + } + } + } + _ if is_last => op, + _ => into_ok(op), } }); @@ -79,6 +104,7 @@ fn build_struct( let store_tag = store_tag.map(into_ok); quote! { #validate_with + #compute_tag_version #store_tag #(#members)* } @@ -86,7 +112,7 @@ fn build_struct( } } -fn store_tag_op(tag: attr::TlbTag) -> Option { +fn store_tag(tag: attr::TlbTag) -> Option { let bits = tag.bits as u16; let op = match bits { @@ -117,6 +143,64 @@ fn store_tag_op(tag: attr::TlbTag) -> Option { Some(quote!(__builder.#op)) } +// Returns (tag_version, stop_tag) +fn store_tags_versioned( + tags: &[attr::TlbTag], + fields: &[ast::Field<'_>], + tag_version: &mut Option, +) -> (Option, Option) { + let Some(first_tag) = tags.first() else { + return (None, None); + }; + + let mut used_tags = BTreeSet::new(); + let mut version_guards = Vec::new(); + for field in fields { + if let Some(since) = field.attrs.since_tag { + used_tags.insert(since); + + let tag_version = + tag_version.get_or_insert_with(|| quote::format_ident!("__tag_version")); + + let ident = &field.member; + let ty = field.ty; + + // TODO: Optimize codegen + version_guards.push(quote! { + if self.#ident != <#ty as Default>::default() { + #tag_version = std::cmp::max(#tag_version, #since); + } + }); + } + } + + let Some(tag_version) = &tag_version else { + return (None, store_tag(*first_tag)); + }; + + used_tags.remove(&0); + let match_arms = used_tags.into_iter().filter_map(|tag_index| { + let store_op = store_tag(tags[tag_index])?; + Some(quote! { #tag_index => #store_op }) + }); + let Some(store_first) = store_tag(*first_tag) else { + return (None, None); + }; + + let store_tag = quote! { + match #tag_version { + #(#match_arms),*, + _ => #store_first, + } + }; + + let compute_tag_version = quote! { + let mut #tag_version = 0usize; + #(#version_guards)* + }; + (Some(compute_tag_version), Some(store_tag)) +} + fn store_op(field_ident: &TokenStream, ty: &syn::Type) -> TokenStream { #[allow(clippy::unnecessary_operation)] 'fallback: { diff --git a/proc/src/internals/ast.rs b/proc/src/internals/ast.rs index 79807377..e9305f0b 100644 --- a/proc/src/internals/ast.rs +++ b/proc/src/internals/ast.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use syn::punctuated::Punctuated; use syn::Token; @@ -38,10 +40,84 @@ impl<'a> Container<'a> { original: item, }; - // TODO: check container attributes + container.validate(cx); Some(container) } + + fn validate(&self, cx: &Ctxt) { + match &self.data { + Data::Enum(variants) => { + if matches!(self.attrs.tlb_tag, attr::ContainerTag::Multiple(_)) { + cx.error_spanned_by(self.original, "multi-tags for enum are not supported"); + } + + for var in variants { + for field in &var.fields { + if field.attrs.since_tag.is_some() { + cx.error_spanned_by( + field.original, + "since_tag is not supported for enum fields", + ); + } + } + } + } + Data::Struct(_, fields) => { + let mut unused_tags = HashSet::new(); + let tag_count = match &self.attrs.tlb_tag { + attr::ContainerTag::None => 0, + attr::ContainerTag::Single(_) => 1, + attr::ContainerTag::Multiple(tags) => { + let mut tag_len = None; + for (i, tag) in tags.iter().enumerate() { + if i > 0 { + unused_tags.insert(i); + } + + match tag_len { + None => tag_len = Some(tag.bits), + Some(bits) if tag.bits != bits => { + cx.error_spanned_by( + self.original, + "all tags must have the same bit length", + ); + } + Some(_) => {} + } + } + + tags.len() + } + }; + + for field in fields { + if let Some(since) = field.attrs.since_tag { + unused_tags.remove(&since); + + if tag_count == 0 { + cx.error_spanned_by( + field.original, + "since_tag is specified but there are no tags for this struct", + ); + } else if since >= tag_count { + cx.error_spanned_by( + field.original, + format!( + "since_tag is out of bounds (max tag index is {})", + tag_count - 1 + ), + ); + } + } + } + + for i in unused_tags { + cx.error_spanned_by(self.original, format!("tag {i} is not used")); + } + } + } + } } fn enum_from_ast<'a>( diff --git a/proc/src/internals/attr.rs b/proc/src/internals/attr.rs index a837865f..3a57365e 100644 --- a/proc/src/internals/attr.rs +++ b/proc/src/internals/attr.rs @@ -6,7 +6,7 @@ use super::ctxt::*; use super::symbol::*; pub struct Container { - pub tlb_tag: Option, + pub tlb_tag: ContainerTag, pub tlb_validate_with: Option, } @@ -28,7 +28,7 @@ impl Container { if let Err(e) = attr.parse_nested_meta(|meta| { if meta.path == TAG { - // Parse `#[tlb(tag = "#ab"]` + // Parse `#[tlb(tag = "#ab"]` or `#[tlb(tag = ["#1", "#2"])]` if let Some(value) = parse_lit_into_tlb_tag(cx, TAG, &meta)? { tlb_tag.set(&meta.path, value); } @@ -50,7 +50,7 @@ impl Container { } Self { - tlb_tag: tlb_tag.get(), + tlb_tag: tlb_tag.get().unwrap_or_default(), tlb_validate_with: tlb_validate_with.get(), } } @@ -83,10 +83,14 @@ impl Variant { } } -pub struct Field; +pub struct Field { + pub since_tag: Option, +} impl Field { pub fn from_ast(cx: &Ctxt, field: &syn::Field) -> Self { + let mut since_tag = Attr::none(cx, SINCE_TAG); + for attr in &field.attrs { if attr.path() != TLB { continue; @@ -99,17 +103,35 @@ impl Field { } if let Err(e) = attr.parse_nested_meta(|meta| { - let path = meta.path.to_token_stream().to_string().replace(' ', ""); - Err(meta.error(format_args!("unknown tl field attribute `{}`", path))) + if meta.path == SINCE_TAG { + // Parse `#[tlb(since_tag = 0)]` + if let Some(value) = parse_number(cx, SINCE_TAG, &meta)? { + since_tag.set(&meta.path, value); + } + } else { + let path = meta.path.to_token_stream().to_string().replace(' ', ""); + return Err(meta.error(format_args!("unknown tl field attribute `{}`", path))); + } + Ok(()) }) { cx.syn_error(e); } } - Self + Self { + since_tag: since_tag.get(), + } } } +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub enum ContainerTag { + #[default] + None, + Single(TlbTag), + Multiple(Vec), +} + #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct TlbTag { pub value: u32, @@ -120,35 +142,57 @@ fn parse_lit_into_tlb_tag( cx: &Ctxt, attr_name: Symbol, meta: &ParseNestedMeta, -) -> syn::Result> { - let Some(lit) = get_lit_str(cx, attr_name, meta)? else { - return Ok(None); - }; - let string = lit.value(); - let string = string.trim(); - if let Some(hex_tag) = string.strip_prefix('#') { - if let Ok(value) = u32::from_str_radix(hex_tag, 16) { - return Ok(Some(TlbTag { - value, - bits: (hex_tag.len() * 4) as u8, - })); - } +) -> syn::Result> { + fn parse_tag(cx: &Ctxt, lit: syn::LitStr) -> Option { + let string = lit.value(); + let string = string.trim(); + if let Some(hex_tag) = string.strip_prefix('#') { + if let Ok(value) = u32::from_str_radix(hex_tag, 16) { + return Some(TlbTag { + value, + bits: (hex_tag.len() * 4) as u8, + }); + } - cx.error_spanned_by(lit, format!("failed to parse hex TLB tag: {string}")); - } else if let Some(binary_tag) = string.strip_prefix('$') { - if let Ok(value) = u32::from_str_radix(binary_tag, 2) { - return Ok(Some(TlbTag { - value, - bits: binary_tag.len() as u8, - })); + cx.error_spanned_by(lit, format!("failed to parse hex TLB tag: {string}")); + } else if let Some(binary_tag) = string.strip_prefix('$') { + if let Ok(value) = u32::from_str_radix(binary_tag, 2) { + return Some(TlbTag { + value, + bits: binary_tag.len() as u8, + }); + } + + cx.error_spanned_by(lit, format!("failed to parse binary TLB tag: {string}")); + } else { + cx.error_spanned_by(lit, format!("failed to parse TLB tag: {string}")); } - cx.error_spanned_by(lit, format!("failed to parse binary TLB tag: {string}")); - } else { - cx.error_spanned_by(lit, format!("failed to parse TLB tag: {string}")); + None } - Ok(None) + Ok(match ungroup_meta(meta)? { + syn::Expr::Array(array) => { + if array.elems.is_empty() { + cx.error_spanned_by(array, "tag list is empty"); + return Ok(None); + } + + let mut tags = Vec::with_capacity(array.elems.len()); + let mut is_ok = true; + for value in array.elems { + let res = get_lit_str2(cx, attr_name, attr_name, &value) + .and_then(|lit| parse_tag(cx, lit)); + is_ok &= res.is_some(); + tags.extend(res); + } + + is_ok.then_some(ContainerTag::Multiple(tags)) + } + value => get_lit_str2(cx, attr_name, attr_name, &value) + .and_then(|lit| parse_tag(cx, lit)) + .map(ContainerTag::Single), + }) } fn parse_lit_into_expr( @@ -165,6 +209,31 @@ fn parse_lit_into_expr( Ok(Some(expr)) } +fn parse_number( + cx: &Ctxt, + attr_name: Symbol, + meta: &ParseNestedMeta, +) -> syn::Result> { + let value = ungroup_meta(meta)?; + + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(lit), + .. + }) = value + { + lit.base10_parse::().map(Some) + } else { + cx.error_spanned_by( + value, + format!( + "expected {} attribute to be a number: `{} = \"...\"`", + attr_name, attr_name + ), + ); + Ok(None) + } +} + fn spanned_tokens(s: &syn::LitStr) -> syn::parse::Result { let stream = syn::parse_str(&s.value())?; Ok(respan_token_stream(stream, s.span())) @@ -190,20 +259,16 @@ fn get_lit_str( attr_name: Symbol, meta: &ParseNestedMeta, ) -> syn::Result> { - get_lit_str2(cx, attr_name, attr_name, meta) + let value = ungroup_meta(meta)?; + Ok(get_lit_str2(cx, attr_name, attr_name, &value)) } fn get_lit_str2( cx: &Ctxt, attr_name: Symbol, meta_item_name: Symbol, - meta: &ParseNestedMeta, -) -> syn::Result> { - let expr: syn::Expr = meta.value()?.parse()?; - let mut value = &expr; - while let syn::Expr::Group(e) = value { - value = &e.expr; - } + value: &syn::Expr, +) -> Option { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. @@ -216,16 +281,26 @@ fn get_lit_str2( format!("unexpected suffix `{}` on string literal", suffix), ); } - Ok(Some(lit.clone())) + Some(lit.clone()) } else { cx.error_spanned_by( - expr, + value, format!( "expected {} attribute to be a string: `{} = \"...\"`", attr_name, meta_item_name ), ); - Ok(None) + None + } +} + +fn ungroup_meta(meta: &ParseNestedMeta) -> syn::Result { + let mut value = meta.value()?.parse()?; + loop { + match value { + syn::Expr::Group(e) => value = *e.expr, + value => return Ok(value), + } } } diff --git a/proc/src/internals/symbol.rs b/proc/src/internals/symbol.rs index 68d53e46..9d0330f4 100644 --- a/proc/src/internals/symbol.rs +++ b/proc/src/internals/symbol.rs @@ -2,6 +2,7 @@ pub const TLB: Symbol = Symbol("tlb"); pub const VALIDATE_WITH: Symbol = Symbol("validate_with"); pub const TAG: Symbol = Symbol("tag"); +pub const SINCE_TAG: Symbol = Symbol("since_tag"); #[derive(Copy, Clone)] pub struct Symbol(&'static str); diff --git a/src/abi/contract.rs b/src/abi/contract.rs index e9973036..459b770c 100644 --- a/src/abi/contract.rs +++ b/src/abi/contract.rs @@ -77,7 +77,7 @@ impl Contract { return Ok(data.clone()); } - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut key_builder = CellBuilder::new(); for token in tokens { @@ -128,7 +128,7 @@ impl Contract { .map(|(name, value)| (name.as_ref(), value)) .collect::>(); - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut key_builder = CellBuilder::new(); // Write explicitly provided values @@ -443,7 +443,7 @@ impl Function { serializer.reserve_value(&token.value); } - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); serializer.write_value(&output_id, context)?; serializer.write_tuple(tokens, context)?; serializer.finalize(context).map_err(From::from) @@ -456,7 +456,10 @@ impl Function { } /// Encodes an unsigned body with invocation of this method as an external message. - pub fn encode_external<'f, 'a: 'f>(&'f self, tokens: &'a [NamedAbiValue]) -> ExternalInput { + pub fn encode_external<'f, 'a: 'f>( + &'f self, + tokens: &'a [NamedAbiValue], + ) -> ExternalInput<'f, 'a> { ExternalInput { function: self, tokens, @@ -646,7 +649,7 @@ pub struct ExternalInput<'f, 'a> { address: Option<&'a StdAddr>, } -impl<'f, 'a> ExternalInput<'f, 'a> { +impl<'a> ExternalInput<'_, 'a> { /// Builds an external message to the specified address. pub fn build_message(&self, address: &StdAddr) -> Result { Ok(ok!(self.build_input_ext(Some(address))).with_dst(address.clone())) @@ -683,7 +686,7 @@ impl<'f, 'a> ExternalInput<'f, 'a> { fn build_input_ext(&self, address: Option<&StdAddr>) -> Result { let (expire_at, payload) = self.build_payload(true)?; - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let hash = if self.function.abi_version >= AbiVersion::V2_3 { let mut to_sign = CellBuilder::new(); match address { @@ -752,7 +755,7 @@ impl<'f, 'a> ExternalInput<'f, 'a> { serializer.reserve_value(&token.value); } - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); if !reserve_signature { let value = if abi_version.major == 1 { diff --git a/src/abi/mod.rs b/src/abi/mod.rs index 602c2503..fc3ba2c8 100644 --- a/src/abi/mod.rs +++ b/src/abi/mod.rs @@ -12,7 +12,9 @@ pub use self::traits::{ FromAbi, FromAbiIter, FromPlainAbi, IgnoreName, IntoAbi, IntoPlainAbi, WithAbiType, WithPlainAbiType, }; -pub use self::ty::{AbiHeaderType, AbiType, NamedAbiType, PlainAbiType}; +pub use self::ty::{ + AbiHeaderType, AbiType, AbiTypeFlatten, NamedAbiType, NamedAbiTypeFlatten, PlainAbiType, +}; pub use self::value::{AbiHeader, AbiValue, NamedAbiValue, PlainAbiValue}; pub mod error; @@ -158,7 +160,7 @@ where self.0.iter().all(|(key, value)| { other .get(key) - .map_or(false, |v| WithoutName::wrap(value) == WithoutName::wrap(v)) + .is_some_and(|v| WithoutName::wrap(value) == WithoutName::wrap(v)) }) } } diff --git a/src/abi/tests/mod.rs b/src/abi/tests/mod.rs index 35979dd4..984130e3 100644 --- a/src/abi/tests/mod.rs +++ b/src/abi/tests/mod.rs @@ -106,7 +106,7 @@ fn encode_external_input() { .store_reference({ let mut builder = CellBuilder::new(); StdAddr::default() - .store_into(&mut builder, &mut Cell::empty_context()) + .store_into(&mut builder, Cell::empty_context()) .unwrap(); builder.store_u8(1).unwrap(); builder.build().unwrap() @@ -152,7 +152,7 @@ fn decode_external_input() { .store_reference({ let mut builder = CellBuilder::new(); StdAddr::default() - .store_into(&mut builder, &mut Cell::empty_context()) + .store_into(&mut builder, Cell::empty_context()) .unwrap(); builder.store_u8(1).unwrap(); builder.build().unwrap() @@ -183,7 +183,7 @@ fn encode_unsigned_external_input() { builder.store_u64(321).unwrap(); builder.store_reference(Cell::default()).unwrap(); StdAddr::default() - .store_into(&mut builder, &mut Cell::empty_context()) + .store_into(&mut builder, Cell::empty_context()) .unwrap(); builder.store_u8(1).unwrap(); builder.build().unwrap() @@ -223,7 +223,7 @@ fn decode_unsigned_external_input() { builder.store_u64(321).unwrap(); builder.store_reference(Cell::default()).unwrap(); StdAddr::default() - .store_into(&mut builder, &mut Cell::empty_context()) + .store_into(&mut builder, Cell::empty_context()) .unwrap(); builder.store_u8(1).unwrap(); builder.build().unwrap() diff --git a/src/abi/traits.rs b/src/abi/traits.rs index e918d55b..f16dceb6 100644 --- a/src/abi/traits.rs +++ b/src/abi/traits.rs @@ -29,7 +29,10 @@ pub trait IgnoreName { } impl IgnoreName for &'_ T { - type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + type Unnamed<'a> + = T::Unnamed<'a> + where + Self: 'a; #[inline] fn ignore_name(&self) -> Self::Unnamed<'_> { @@ -41,7 +44,10 @@ impl IgnoreName for Vec where [T]: IgnoreName, { - type Unnamed<'a> = <[T] as IgnoreName>::Unnamed<'a> where Self: 'a; + type Unnamed<'a> + = <[T] as IgnoreName>::Unnamed<'a> + where + Self: 'a; #[inline] fn ignore_name(&self) -> Self::Unnamed<'_> { @@ -50,7 +56,10 @@ where } impl IgnoreName for Box { - type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + type Unnamed<'a> + = T::Unnamed<'a> + where + Self: 'a; #[inline] fn ignore_name(&self) -> Self::Unnamed<'_> { @@ -59,7 +68,10 @@ impl IgnoreName for Box { } impl IgnoreName for Arc { - type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + type Unnamed<'a> + = T::Unnamed<'a> + where + Self: 'a; #[inline] fn ignore_name(&self) -> Self::Unnamed<'_> { @@ -68,7 +80,10 @@ impl IgnoreName for Arc { } impl IgnoreName for Rc { - type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + type Unnamed<'a> + = T::Unnamed<'a> + where + Self: 'a; #[inline] fn ignore_name(&self) -> Self::Unnamed<'_> { @@ -77,7 +92,10 @@ impl IgnoreName for Rc { } impl IgnoreName for Option { - type Unnamed<'a> = Option> where Self: 'a; + type Unnamed<'a> + = Option> + where + Self: 'a; #[inline] fn ignore_name(&self) -> Self::Unnamed<'_> { @@ -248,19 +266,31 @@ impl WithAbiType for Option { impl WithAbiType for Vec { fn abi_type() -> AbiType { - AbiType::Array(Arc::new(T::abi_type())) + if typeid::of::() == typeid::of::() { + AbiType::Bytes + } else { + AbiType::Array(Arc::new(T::abi_type())) + } } } impl WithAbiType for [T] { fn abi_type() -> AbiType { - AbiType::Array(Arc::new(T::abi_type())) + if typeid::of::() == typeid::of::() { + AbiType::Bytes + } else { + AbiType::Array(Arc::new(T::abi_type())) + } } } impl WithAbiType for [T; N] { fn abi_type() -> AbiType { - AbiType::FixedArray(Arc::new(T::abi_type()), N) + if typeid::of::() == typeid::of::() { + AbiType::FixedBytes(N) + } else { + AbiType::FixedArray(Arc::new(T::abi_type()), N) + } } } @@ -632,10 +662,16 @@ impl IntoAbi for str { impl IntoAbi for [T] { fn as_abi(&self) -> AbiValue { - AbiValue::Array( - Arc::new(T::abi_type()), - self.iter().map(T::as_abi).collect(), - ) + if typeid::of::() == typeid::of::() { + // SAFETY: T is definitely u8 + let bytes = unsafe { std::slice::from_raw_parts(self.as_ptr().cast(), self.len()) }; + AbiValue::Bytes(Bytes::copy_from_slice(bytes)) + } else { + AbiValue::Array( + Arc::new(T::abi_type()), + self.iter().map(T::as_abi).collect(), + ) + } } #[inline] @@ -647,22 +683,56 @@ impl IntoAbi for [T] { } } +impl IntoAbi for [T; N] { + fn as_abi(&self) -> AbiValue { + if typeid::of::() == typeid::of::() { + // SAFETY: T is definitely u8 + let bytes = unsafe { std::slice::from_raw_parts(self.as_ptr().cast(), self.len()) }; + AbiValue::FixedBytes(Bytes::copy_from_slice(bytes)) + } else { + AbiValue::FixedArray( + Arc::new(T::abi_type()), + self.iter().map(T::as_abi).collect(), + ) + } + } + + fn into_abi(self) -> AbiValue + where + Self: Sized, + { + if typeid::of::() == typeid::of::() { + // SAFETY: T is definitely u8 + let bytes = unsafe { std::slice::from_raw_parts(self.as_ptr().cast(), self.len()) }; + AbiValue::FixedBytes(Bytes::copy_from_slice(bytes)) + } else { + AbiValue::FixedArray( + Arc::new(T::abi_type()), + self.into_iter().map(T::into_abi).collect(), + ) + } + } +} + impl IntoAbi for Vec { + #[inline] fn as_abi(&self) -> AbiValue { - AbiValue::Array( - Arc::new(T::abi_type()), - self.iter().map(T::as_abi).collect(), - ) + <[T]>::as_abi(self.as_slice()) } fn into_abi(self) -> AbiValue where Self: Sized, { - AbiValue::Array( - Arc::new(T::abi_type()), - self.into_iter().map(T::into_abi).collect(), - ) + if typeid::of::() == typeid::of::() { + // SAFETY: `T` is the same type as `u8`. + AbiValue::Bytes(Bytes::from(unsafe { cast_vec::(self) })) + } else { + AbiValue::Array( + Arc::new(T::abi_type()), + self.into_iter().map(T::into_abi).collect(), + ) + } } } @@ -928,6 +998,26 @@ impl FromAbi for HashBytes { } } +impl FromPlainAbi for HashBytes { + fn from_plain_abi(value: PlainAbiValue) -> Result { + match &value { + PlainAbiValue::Uint(256, v) => { + let mut result = HashBytes::ZERO; + + let bytes = v.to_bytes_be(); + let bytes_len = bytes.len(); + match 32usize.checked_sub(bytes_len) { + None => result.0.copy_from_slice(&bytes[bytes_len - 32..]), + Some(pad) => result.0[pad..].copy_from_slice(&bytes), + }; + + Ok(result) + } + value => Err(expected_plain_type("uint256", value)), + } + } +} + impl FromAbi for Cell { fn from_abi(value: AbiValue) -> Result { match value { @@ -1019,15 +1109,26 @@ impl FromPlainAbi for VarAddr { impl FromAbi for Vec { fn from_abi(value: AbiValue) -> Result { - let items = match value { - AbiValue::Array(_, items) | AbiValue::FixedArray(_, items) => items, - value => return Err(expected_type("array", &value)), - }; - let mut result = Vec::with_capacity(items.len()); - for item in items { - result.push(ok!(T::from_abi(item))); + if typeid::of::() == typeid::of::() { + match value { + AbiValue::Bytes(bytes) | AbiValue::FixedBytes(bytes) => { + let bytes = Vec::::from(bytes); + // SAFETY: `T` is the same type as `u8`. + Ok(unsafe { cast_vec::(bytes) }) + } + value => Err(expected_type("bytes or fixedbytes", &value)), + } + } else { + let items = match value { + AbiValue::Array(_, items) | AbiValue::FixedArray(_, items) => items, + value => return Err(expected_type("array", &value)), + }; + let mut result = Vec::with_capacity(items.len()); + for item in items { + result.push(ok!(T::from_abi(item))); + } + Ok(result) } - Ok(result) } } @@ -1159,6 +1260,26 @@ where } } +/// # Safety +/// +/// The following must be true: +/// - `T1` must have the same memory layout as `T2`. +unsafe fn cast_vec(v: Vec) -> Vec { + // The code is the same as in the offical example: + // https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.from_raw_parts + + // Prevent running `self`'s destructor so we are in complete control + // of the allocation. + let mut v = std::mem::ManuallyDrop::new(v); + + // Pull out the various important pieces of information about `v` + let p = v.as_mut_ptr().cast::(); + let len = v.len(); + let cap = v.capacity(); + + Vec::::from_raw_parts(p, len, cap) +} + #[cfg(test)] mod tests { use ahash::HashSet; diff --git a/src/abi/ty.rs b/src/abi/ty.rs index 8ddf113b..1e4acf02 100644 --- a/src/abi/ty.rs +++ b/src/abi/ty.rs @@ -38,6 +38,27 @@ impl NamedAbiType { pub fn from_index(index: usize, ty: AbiType) -> Self { Self::new(format!("value{index}"), ty) } + + /// Returns an iterator with the first-level tuple flattened. + /// + /// Can be used to pass an ABI struct as arguments to the + /// [`FunctionBuilder::with_inputs`] or [`FunctionBuilder::with_outputs`]. + /// + /// [`FunctionBuilder::with_inputs`]: fn@crate::abi::FunctionBuilder::with_inputs + /// [`FunctionBuilder::with_outputs`]: fn@crate::abi::FunctionBuilder::with_outputs + pub fn flatten(self) -> NamedAbiTypeFlatten { + match self.ty { + AbiType::Tuple(tuple) => { + let mut items = tuple.to_vec(); + items.reverse(); + NamedAbiTypeFlatten::Tuple(items) + } + ty => NamedAbiTypeFlatten::Single(Some(NamedAbiType { + name: self.name, + ty, + })), + } + } } impl AsRef for NamedAbiType { @@ -178,6 +199,34 @@ impl std::borrow::Borrow> for WithoutName { } } +/// An iterator that flattens the first-level tuple. +#[derive(Clone)] +pub enum NamedAbiTypeFlatten { + #[doc(hidden)] + Single(Option), + #[doc(hidden)] + Tuple(Vec), +} + +impl Iterator for NamedAbiTypeFlatten { + type Item = NamedAbiType; + + fn size_hint(&self) -> (usize, Option) { + let size = match self { + Self::Single(item) => item.is_some() as usize, + Self::Tuple(items) => items.len(), + }; + (size, Some(size)) + } + + fn next(&mut self) -> Option { + match self { + Self::Single(item) => item.take(), + Self::Tuple(items) => items.pop(), + } + } +} + /// Contract header value type. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum AbiHeaderType { @@ -396,6 +445,24 @@ impl AbiType { } } + /// Returns an iterator with the first-level tuple flattened. + /// + /// Can be used to pass an ABI struct as arguments to the + /// [`FunctionBuilder::with_unnamed_inputs`] or [`FunctionBuilder::with_unnamed_outputs`] + /// + /// [`FunctionBuilder::with_unnamed_inputs`]: fn@crate::abi::FunctionBuilder::with_unnamed_inputs + /// [`FunctionBuilder::with_unnamed_outputs`]: fn@crate::abi::FunctionBuilder::with_unnamed_outputs + pub fn flatten(self) -> AbiTypeFlatten { + match self { + AbiType::Tuple(tuple) => { + let mut items = tuple.to_vec(); + items.reverse(); + AbiTypeFlatten::Tuple(items) + } + ty => AbiTypeFlatten::Single(Some(ty)), + } + } + /// Simple `varuintN` type constructor. #[inline] pub fn varuint(size: u8) -> Self { @@ -697,6 +764,34 @@ impl Hash for WithoutName { } } +/// An iterator that flattens the first-level tuple. +#[derive(Clone)] +pub enum AbiTypeFlatten { + #[doc(hidden)] + Single(Option), + #[doc(hidden)] + Tuple(Vec), +} + +impl Iterator for AbiTypeFlatten { + type Item = AbiType; + + fn size_hint(&self) -> (usize, Option) { + let size = match self { + Self::Single(item) => item.is_some() as usize, + Self::Tuple(items) => items.len(), + }; + (size, Some(size)) + } + + fn next(&mut self) -> Option { + match self { + Self::Single(item) => item.take(), + Self::Tuple(items) => items.pop().map(|item| item.ty), + } + } +} + /// ABI type which has a fixed bits representation /// and therefore can be used as a map key. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] diff --git a/src/abi/value/de.rs b/src/abi/value/de.rs index 072a657c..d0cb5348 100644 --- a/src/abi/value/de.rs +++ b/src/abi/value/de.rs @@ -1091,7 +1091,7 @@ mod tests { ]); // - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut builder = CellBuilder::new(); builder.store_u32(0)?; builder.store_reference(Cell::empty_cell())?; @@ -1153,7 +1153,7 @@ mod tests { let mut builder = CellBuilder::new(); builder.store_u32(0)?; builder.store_reference(Cell::empty_cell())?; - addr_map.store_into(&mut builder, &mut Cell::empty_context())?; + addr_map.store_into(&mut builder, Cell::empty_context())?; builder.build()? }; @@ -1221,7 +1221,7 @@ mod tests { // let cell = { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut builder = CellBuilder::new(); builder.store_u32(0)?; builder.store_reference(Cell::empty_cell())?; diff --git a/src/abi/value/ser.rs b/src/abi/value/ser.rs index 59ceb981..5214e5c5 100644 --- a/src/abi/value/ser.rs +++ b/src/abi/value/ser.rs @@ -20,7 +20,7 @@ use crate::prelude::CellFamily; impl NamedAbiValue { /// Tries to store multiple values into a new builder according to the specified ABI version. pub fn tuple_to_builder(items: &[Self], version: AbiVersion) -> Result { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut serializer = AbiSerializer::new(version); for item in items { serializer.reserve_value(&item.value); @@ -50,7 +50,7 @@ impl NamedAbiValue { impl AbiValue { /// Tries to store multiple values into a new builder according to the specified ABI version. pub fn tuple_to_builder(values: &[Self], version: AbiVersion) -> Result { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut serializer = AbiSerializer::new(version); for value in values { serializer.reserve_value(value); @@ -68,7 +68,7 @@ impl AbiValue { /// Tries to store this value into a new builder according to the specified ABI version. pub fn make_builder(&self, version: AbiVersion) -> Result { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut serializer = AbiSerializer::new(version); serializer.reserve_value(self); ok!(serializer.write_value(self, context)); @@ -264,7 +264,7 @@ impl AbiSerializer { } } - pub fn finalize(mut self, context: &mut dyn CellContext) -> Result { + pub fn finalize(mut self, context: &dyn CellContext) -> Result { debug_assert_eq!(self.remaining_total, CellTreeStats::ZERO); let mut result = self.stack.pop().unwrap_or_default(); @@ -275,7 +275,7 @@ impl AbiSerializer { Ok(result) } - pub fn take_finalize(&mut self, context: &mut dyn CellContext) -> Result { + pub fn take_finalize(&mut self, context: &dyn CellContext) -> Result { debug_assert_eq!(self.remaining_total, CellTreeStats::ZERO); self.current = Size::ZERO; @@ -354,7 +354,7 @@ impl AbiSerializer { pub(crate) fn write_value( &mut self, value: &AbiValue, - c: &mut dyn CellContext, + c: &dyn CellContext, ) -> Result<(), Error> { match value { AbiValue::Uint(n, value) => self.write_int(*n, Sign::Plus, value), @@ -379,7 +379,7 @@ impl AbiSerializer { pub(crate) fn write_tuple( &mut self, items: &[NamedAbiValue], - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { for item in items { ok!(self.write_value(&item.value, context)); @@ -436,7 +436,7 @@ impl AbiSerializer { }, refs: 0, }); - address.store_into(target, &mut Cell::empty_context()) + address.store_into(target, Cell::empty_context()) } fn write_tokens(&mut self, tokens: &Tokens) -> Result<(), Error> { @@ -448,10 +448,10 @@ impl AbiSerializer { }, refs: 0, }); - tokens.store_into(target, &mut Cell::empty_context()) + tokens.store_into(target, Cell::empty_context()) } - fn write_bytes(&mut self, mut data: &[u8], context: &mut dyn CellContext) -> Result<(), Error> { + fn write_bytes(&mut self, mut data: &[u8], context: &dyn CellContext) -> Result<(), Error> { const MAX_BYTES_PER_BUILDER: usize = (MAX_BIT_LEN / 8) as usize; let mut len = data.len(); @@ -489,7 +489,7 @@ impl AbiSerializer { value_ty: &AbiType, values: &[AbiValue], fixed_len: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let inline_value = fits_into_dict_leaf(32, value_ty.max_bits()); @@ -535,7 +535,7 @@ impl AbiSerializer { key_ty: &PlainAbiType, value_ty: &AbiType, value: &BTreeMap, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let key_bits = key_ty.key_bits(); let inline_value = fits_into_dict_leaf(key_bits, value_ty.max_bits()); @@ -577,7 +577,7 @@ impl AbiSerializer { &mut self, ty: &AbiType, value: Option<&AbiValue>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let (max_size, inline) = { let ty_size = ty.max_size(); @@ -630,7 +630,7 @@ impl AbiSerializer { } } - fn write_ref(&mut self, value: &AbiValue, context: &mut dyn CellContext) -> Result<(), Error> { + fn write_ref(&mut self, value: &AbiValue, context: &dyn CellContext) -> Result<(), Error> { let cell = { let mut serializer = self.begin_child(); serializer.reserve_value(value); @@ -695,7 +695,7 @@ enum InlineOrRef<'a> { } impl Store for InlineOrRef<'_> { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { match self { Self::Inline(slice) => builder.store_slice(slice), Self::Ref(cell) => builder.store_reference(cell.clone()), @@ -704,7 +704,7 @@ impl Store for InlineOrRef<'_> { } impl Store for PlainAbiValue { - fn store_into(&self, builder: &mut CellBuilder, f: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, f: &dyn CellContext) -> Result<(), Error> { match self { Self::Uint(bits, value) => write_int(*bits, Sign::Plus, value, builder), Self::Int(bits, value) => write_int(*bits, value.sign(), value.magnitude(), builder), diff --git a/src/boc/de.rs b/src/boc/de.rs index 5b89c498..36572f1d 100644 --- a/src/boc/de.rs +++ b/src/boc/de.rs @@ -137,10 +137,14 @@ impl<'a> BocHeader<'a> { return Err(Error::TooFewRootCells); } } - if unlikely(root_count > options.max_roots.unwrap_or(MAX_ROOTS)) { - return Err(Error::TooManyRootCells); + + { + let max_roots = options.max_roots.unwrap_or(MAX_ROOTS); + if unlikely(root_count > max_roots) { + return Err(Error::TooManyRootCells); + } + debug_assert!(absent_count == 0 && (1..=max_roots).contains(&root_count)) } - debug_assert!(absent_count == 0 && (1..=MAX_ROOTS).contains(&root_count)); // SAFETY: we have already requested at least {ref_size}*3+{offset_size} // and {ref_size} is in range 1..=8 @@ -248,7 +252,7 @@ impl<'a> BocHeader<'a> { } /// Assembles cell tree from slices using the specified cell context. - pub fn finalize(&self, context: &mut dyn CellContext) -> Result { + pub fn finalize(&self, context: &dyn CellContext) -> Result { let ref_size = self.ref_size; let cell_count = self.cells.len() as u32; diff --git a/src/boc/mod.rs b/src/boc/mod.rs index 3fb84af3..ce8e34d1 100644 --- a/src/boc/mod.rs +++ b/src/boc/mod.rs @@ -2,11 +2,17 @@ use crate::cell::{Cell, CellBuilder, CellContext, CellFamily, DynCell, HashBytes, Load, Store}; +#[cfg(feature = "serde")] +pub use self::serde::SerdeBoc; + /// BOC decoder implementation. pub mod de; /// BOC encoder implementation. pub mod ser; +#[cfg(feature = "serde")] +mod serde; + #[cfg(test)] mod tests; @@ -47,41 +53,6 @@ impl BocTag { } } -/// A serde helper to use [`Boc`] inside [`Option`]. -#[cfg(feature = "serde")] -pub struct OptionBoc; - -#[cfg(feature = "serde")] -impl OptionBoc { - /// Serializes an optional cell into an encoded BOC - /// (as base64 for human readable serializers). - pub fn serialize(cell: &Option, serializer: S) -> Result - where - S: serde::Serializer, - T: AsRef, - { - match cell { - Some(cell) => serializer.serialize_some(cell.as_ref()), - None => serializer.serialize_none(), - } - } - - /// Deserializes an optional cell from an encoded BOC - /// (from base64 for human readable deserializers). - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - use serde::Deserialize; - - #[derive(Deserialize)] - #[repr(transparent)] - struct Wrapper(#[serde(with = "Boc")] Cell); - - Ok(ok!(Option::::deserialize(deserializer)).map(|Wrapper(cell)| cell)) - } -} - /// BOC (Bag Of Cells) helper. pub struct Boc; @@ -118,6 +89,15 @@ impl Boc { } } + /// Encodes the specified cell tree as BOC and + /// returns the `hex` encoded bytes as a string. + pub fn encode_hex(cell: T) -> String + where + T: AsRef, + { + hex::encode(Self::encode(cell)) + } + /// Encodes the specified cell tree as BOC and /// returns the `base64` encoded bytes as a string. #[cfg(any(feature = "base64", test))] @@ -128,6 +108,18 @@ impl Boc { crate::util::encode_base64(Self::encode(cell)) } + /// Encodes the specified cell tree as BOC and + /// returns the `hex` encoded bytes as a string. + /// + /// Uses `rayon` under the hood to parallelize encoding. + #[cfg(feature = "rayon")] + pub fn encode_hex_rayon(cell: T) -> String + where + T: AsRef, + { + hex::encode(Self::encode_rayon(cell)) + } + /// Encodes the specified cell tree as BOC and /// returns the `base64` encoded bytes as a string. /// @@ -185,6 +177,18 @@ impl Boc { encode_pair_impl(cell1.as_ref(), cell2.as_ref()) } + /// Decodes a `hex` encoded BOC into a cell tree + /// using an empty cell context. + pub fn decode_hex>(data: T) -> Result { + fn decode_hex_impl(data: &[u8]) -> Result { + match hex::decode(data) { + Ok(data) => Boc::decode_ext(data.as_slice(), Cell::empty_context()), + Err(_) => Err(de::Error::UnknownBocTag), + } + } + decode_hex_impl(data.as_ref()) + } + /// Decodes a `base64` encoded BOC into a cell tree /// using an empty cell context. #[cfg(any(feature = "base64", test))] @@ -192,7 +196,7 @@ impl Boc { pub fn decode_base64>(data: T) -> Result { fn decode_base64_impl(data: &[u8]) -> Result { match crate::util::decode_base64(data) { - Ok(data) => Boc::decode_ext(data.as_slice(), &mut Cell::empty_context()), + Ok(data) => Boc::decode_ext(data.as_slice(), Cell::empty_context()), Err(_) => Err(de::Error::UnknownBocTag), } } @@ -206,7 +210,7 @@ impl Boc { T: AsRef<[u8]>, { fn decode_impl(data: &[u8]) -> Result { - Boc::decode_ext(data, &mut Cell::empty_context()) + Boc::decode_ext(data, Cell::empty_context()) } decode_impl(data.as_ref()) } @@ -218,13 +222,13 @@ impl Boc { T: AsRef<[u8]>, { fn decode_pair_impl(data: &[u8]) -> Result<(Cell, Cell), de::Error> { - Boc::decode_pair_ext(data, &mut Cell::empty_context()) + Boc::decode_pair_ext(data, Cell::empty_context()) } decode_pair_impl(data.as_ref()) } /// Decodes a cell tree using the specified cell context. - pub fn decode_ext(data: &[u8], context: &mut dyn CellContext) -> Result { + pub fn decode_ext(data: &[u8], context: &dyn CellContext) -> Result { use self::de::*; let header = ok!(de::BocHeader::decode( @@ -248,7 +252,7 @@ impl Boc { /// Decodes a pair of cell trees using the specified cell context. pub fn decode_pair_ext( data: &[u8], - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(Cell, Cell), de::Error> { use self::de::*; @@ -273,40 +277,26 @@ impl Boc { /// Serializes cell into an encoded BOC (as base64 for human readable serializers). #[cfg(feature = "serde")] - pub fn serialize(cell: &T, serializer: S) -> Result + pub fn serialize(value: T, serializer: S) -> Result where - S: serde::Serializer, - T: AsRef + ?Sized, + SerdeBoc: ::serde::Serialize, + S: ::serde::Serializer, { - use serde::Serialize; + use ::serde::Serialize; - cell.as_ref().serialize(serializer) + SerdeBoc::from(value).serialize(serializer) } /// Deserializes cell from an encoded BOC (from base64 for human readable deserializers). #[cfg(feature = "serde")] - pub fn deserialize<'de, D>(deserializer: D) -> Result + pub fn deserialize<'de, T, D>(deserializer: D) -> Result where - D: serde::Deserializer<'de>, + SerdeBoc: ::serde::Deserialize<'de>, + D: ::serde::Deserializer<'de>, { - use serde::de::Error; + use ::serde::Deserialize; - let is_human_readable = deserializer.is_human_readable(); - let mut boc = ok!(borrow_cow_bytes(deserializer)); - - if is_human_readable { - match crate::util::decode_base64(boc) { - Ok(bytes) => { - boc = std::borrow::Cow::Owned(bytes); - } - Err(_) => return Err(Error::custom("invalid base64 string")), - } - } - - match Boc::decode(boc) { - Ok(cell) => Ok(cell), - Err(e) => Err(Error::custom(e)), - } + SerdeBoc::::deserialize(deserializer).map(SerdeBoc::into_inner) } } @@ -314,6 +304,16 @@ impl Boc { pub struct BocRepr; impl BocRepr { + /// Encodes the specified cell tree as BOC using an empty cell context and + /// returns the `hex` encoded bytes as a string. + pub fn encode_hex(data: T) -> Result + where + T: Store, + { + let boc = ok!(Self::encode_ext(data, Cell::empty_context())); + Ok(hex::encode(boc)) + } + /// Encodes the specified cell tree as BOC using an empty cell context and /// returns the `base64` encoded bytes as a string. #[cfg(any(feature = "base64", test))] @@ -321,10 +321,23 @@ impl BocRepr { where T: Store, { - let boc = ok!(Self::encode_ext(data, &mut Cell::empty_context())); + let boc = ok!(Self::encode_ext(data, Cell::empty_context())); Ok(crate::util::encode_base64(boc)) } + /// Encodes the specified cell tree as BOC using an empty cell context and + /// returns the `hex` encoded bytes as a string. + /// + /// Uses `rayon` under the hood to parallelize encoding. + #[cfg(feature = "rayon")] + pub fn encode_hex_rayon(data: T) -> Result + where + T: Store, + { + let boc = ok!(Self::encode_rayon_ext(data, Cell::empty_context())); + Ok(hex::encode(boc)) + } + /// Encodes the specified cell tree as BOC using an empty cell context and /// returns the `base64` encoded bytes as a string. /// @@ -334,7 +347,7 @@ impl BocRepr { where T: Store, { - let boc = ok!(Self::encode_rayon_ext(data, &mut Cell::empty_context())); + let boc = ok!(Self::encode_rayon_ext(data, Cell::empty_context())); Ok(crate::util::encode_base64(boc)) } @@ -343,7 +356,7 @@ impl BocRepr { where T: Store, { - Self::encode_ext(data, &mut Cell::empty_context()) + Self::encode_ext(data, Cell::empty_context()) } /// Encodes the specified cell tree as BOC using an empty cell context. @@ -354,7 +367,27 @@ impl BocRepr { where T: Store, { - Self::encode_rayon_ext(data, &mut Cell::empty_context()) + Self::encode_rayon_ext(data, Cell::empty_context()) + } + + /// Decodes a `hex` encoded BOC into an object + /// using an empty cell context. + #[inline] + pub fn decode_hex(data: D) -> Result + where + for<'a> T: Load<'a>, + D: AsRef<[u8]>, + { + fn decode_hex_impl(data: &[u8]) -> Result + where + for<'a> T: Load<'a>, + { + match hex::decode(data) { + Ok(data) => BocRepr::decode_ext(data.as_slice(), Cell::empty_context()), + Err(_) => Err(BocReprError::InvalidBoc(de::Error::UnknownBocTag)), + } + } + decode_hex_impl::(data.as_ref()) } /// Decodes a `base64` encoded BOC into an object @@ -371,7 +404,7 @@ impl BocRepr { for<'a> T: Load<'a>, { match crate::util::decode_base64(data) { - Ok(data) => BocRepr::decode_ext(data.as_slice(), &mut Cell::empty_context()), + Ok(data) => BocRepr::decode_ext(data.as_slice(), Cell::empty_context()), Err(_) => Err(BocReprError::InvalidBoc(de::Error::UnknownBocTag)), } } @@ -389,22 +422,19 @@ impl BocRepr { where for<'a> T: Load<'a>, { - BocRepr::decode_ext(data, &mut Cell::empty_context()) + BocRepr::decode_ext(data, Cell::empty_context()) } decode_impl::(data.as_ref()) } /// Encodes the specified object as BOC. - pub fn encode_ext( - data: T, - context: &mut dyn CellContext, - ) -> Result, crate::error::Error> + pub fn encode_ext(data: T, context: &dyn CellContext) -> Result, crate::error::Error> where T: Store, { fn encode_ext_impl( data: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, crate::error::Error> { let mut builder = CellBuilder::new(); ok!(data.store_into(&mut builder, context)); @@ -420,14 +450,14 @@ impl BocRepr { #[cfg(feature = "rayon")] pub fn encode_rayon_ext( data: T, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, crate::error::Error> where T: Store, { fn encode_ext_impl( data: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, crate::error::Error> { let mut builder = CellBuilder::new(); ok!(data.store_into(&mut builder, context)); @@ -438,7 +468,7 @@ impl BocRepr { } /// Decodes object from BOC using the specified cell context. - pub fn decode_ext(data: &[u8], context: &mut dyn CellContext) -> Result + pub fn decode_ext(data: &[u8], context: &dyn CellContext) -> Result where for<'a> T: Load<'a>, { @@ -458,12 +488,12 @@ impl BocRepr { #[cfg(feature = "serde")] pub fn serialize(data: &T, serializer: S) -> Result where - S: serde::Serializer, + S: ::serde::Serializer, T: Store, { - use serde::ser::{Error, Serialize}; + use ::serde::ser::{Error, Serialize}; - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut builder = CellBuilder::new(); if data.store_into(&mut builder, context).is_err() { @@ -483,12 +513,12 @@ impl BocRepr { #[cfg(feature = "serde")] pub fn deserialize<'de, D, T>(deserializer: D) -> Result where - D: serde::Deserializer<'de>, + D: ::serde::Deserializer<'de>, for<'a> T: Load<'a>, { - use serde::de::Error; + use ::serde::de::Error; - let cell = ok!(Boc::deserialize(deserializer)); + let cell = ok!(Boc::deserialize::(deserializer)); match cell.as_ref().parse::() { Ok(data) => Ok(data), Err(_) => Err(Error::custom("failed to decode object from cells")), @@ -506,67 +536,3 @@ pub enum BocReprError { #[error("failed to decode object from cells")] InvalidData(#[source] crate::error::Error), } - -#[cfg(feature = "serde")] -fn borrow_cow_bytes<'de: 'a, 'a, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - use std::borrow::Cow; - - use serde::de::{Error, Visitor}; - - struct CowBytesVisitor; - - impl<'a> Visitor<'a> for CowBytesVisitor { - type Value = Cow<'a, [u8]>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a byte array") - } - - fn visit_str(self, v: &str) -> Result - where - E: Error, - { - Ok(Cow::Owned(v.as_bytes().to_vec())) - } - - fn visit_borrowed_str(self, v: &'a str) -> Result - where - E: Error, - { - Ok(Cow::Borrowed(v.as_bytes())) - } - - fn visit_string(self, v: String) -> Result - where - E: Error, - { - Ok(Cow::Owned(v.into_bytes())) - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: Error, - { - Ok(Cow::Owned(v.to_vec())) - } - - fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result - where - E: Error, - { - Ok(Cow::Borrowed(v)) - } - - fn visit_byte_buf(self, v: Vec) -> Result - where - E: Error, - { - Ok(Cow::Owned(v)) - } - } - - deserializer.deserialize_bytes(CowBytesVisitor) -} diff --git a/src/boc/ser.rs b/src/boc/ser.rs index ec0d6d62..8f9afbe5 100644 --- a/src/boc/ser.rs +++ b/src/boc/ser.rs @@ -117,6 +117,49 @@ where ); } + /// Writes cell trees into the writer. + /// + /// NOTE: Use [`BocHeader::encode`] when possible since it's faster. + pub fn encode_to_writer(&self, mut writer: W) -> std::io::Result<()> { + const CELLS_CHUNK_SIZE: usize = 1000; + const P95_CELL_SIZE: usize = 128; + + let mut crc = self.include_crc.then_some(0u32); + let mut total_size = 0; + + let mut reset_chunk = |chunk: &mut Vec| { + if let Some(crc) = &mut crc { + *crc = crc32c::crc32c_append(*crc, chunk); + } + total_size += chunk.len() as u64; + chunk.clear(); + }; + + let mut chunk = Vec::new(); + + // Write header + let header = self.encode_header(&mut chunk); + ok!(writer.write_all(&chunk)); + reset_chunk(&mut chunk); + + // Write cells + for cells in self.rev_cells.rchunks(CELLS_CHUNK_SIZE) { + chunk.reserve(cells.len() * P95_CELL_SIZE); + self.encode_cells_chunk(cells, header.ref_size, &mut chunk); + ok!(writer.write_all(&chunk)); + reset_chunk(&mut chunk); + } + + debug_assert!(chunk.is_empty()); + + if let Some(crc) = crc { + ok!(writer.write_all(&crc.to_le_bytes())); + } + + debug_assert_eq!(total_size, header.total_size); + Ok(()) + } + /// Encodes cell trees into bytes. /// Uses `rayon` under the hood. #[cfg(feature = "rayon")] @@ -158,26 +201,17 @@ where ); } - #[inline] - fn encode_header(&self, target: &mut Vec) -> EncodedHeader { + /// Computes the encoded BOC size and other stuff. + pub fn compute_stats(&self) -> BocHeaderStats { let root_count = self.root_rev_indices.len(); let ref_size = number_of_bytes_to_fit(self.cell_count as u64); - // NOTE: `ref_size` will be in range 1..=4 because `self.cell_count` - // is `u32`, and there is at least one cell (see Self::new) - debug_assert!((1..=4).contains(&ref_size)); - let total_cells_size: u64 = self.total_data_size + let total_cells_size = self.total_data_size + (self.cell_count as u64 * 2) // all descriptor bytes + (ref_size as u64 * self.reference_count); let offset_size = number_of_bytes_to_fit(total_cells_size); - // NOTE: `offset_size` will be in range 1..=8 because `self.cell_count` - // is at least 1, and `total_cells_size` is `u64` - debug_assert!((1..=8).contains(&offset_size)); - - let flags = (ref_size as u8) | (u8::from(self.include_crc) * 0b0100_0000); - // 4 bytes - BOC tag // 1 byte - flags // 1 byte - offset size @@ -194,24 +228,46 @@ where + (offset_size as u64) + total_cells_size + u64::from(self.include_crc) * 4; - target.reserve(total_size as usize); + + BocHeaderStats { + offset_size, + ref_size, + total_cells_size, + total_size, + } + } + + #[inline] + fn encode_header(&self, target: &mut Vec) -> BocHeaderStats { + let stats = self.compute_stats(); + + let root_count = self.root_rev_indices.len(); + + // NOTE: `ref_size` will be in range 1..=4 because `self.cell_count` + // is `u32`, and there is at least one cell (see Self::new) + debug_assert!((1..=4).contains(&stats.ref_size)); + + // NOTE: `offset_size` will be in range 1..=8 because `self.cell_count` + // is at least 1, and `total_cells_size` is `u64` + debug_assert!((1..=8).contains(&stats.offset_size)); + + let flags = (stats.ref_size as u8) | (u8::from(self.include_crc) * 0b0100_0000); + + target.reserve(stats.total_size as usize); target.extend_from_slice(&BocTag::GENERIC); - target.extend_from_slice(&[flags, offset_size as u8]); - target.extend_from_slice(&self.cell_count.to_be_bytes()[4 - ref_size..]); - target.extend_from_slice(&(root_count as u32).to_be_bytes()[4 - ref_size..]); - target.extend_from_slice(&[0; 4][4 - ref_size..]); - target.extend_from_slice(&total_cells_size.to_be_bytes()[8 - offset_size..]); + target.extend_from_slice(&[flags, stats.offset_size as u8]); + target.extend_from_slice(&self.cell_count.to_be_bytes()[4 - stats.ref_size..]); + target.extend_from_slice(&(root_count as u32).to_be_bytes()[4 - stats.ref_size..]); + target.extend_from_slice(&[0; 4][4 - stats.ref_size..]); + target.extend_from_slice(&stats.total_cells_size.to_be_bytes()[8 - stats.offset_size..]); for rev_index in &self.root_rev_indices { let root_index = self.cell_count - rev_index - 1; - target.extend_from_slice(&root_index.to_be_bytes()[4 - ref_size..]); + target.extend_from_slice(&root_index.to_be_bytes()[4 - stats.ref_size..]); } - EncodedHeader { - ref_size, - total_size, - } + stats } #[inline] @@ -327,10 +383,22 @@ impl CellDescriptor { } } +/// An info about the encoded BOC. #[derive(Copy, Clone)] -struct EncodedHeader { - ref_size: usize, - total_size: u64, +pub struct BocHeaderStats { + /// Size of the offset numbers in bytes. + pub offset_size: usize, + /// Size of the reference indices in bytes. + pub ref_size: usize, + /// The total size of cells part in the resulting BOC. + /// + /// NOTE: Use [`total_size`] for the full BOC size. + /// + /// [`total_size`]: Self::total_size + pub total_cells_size: u64, + + /// Total size of the encoded BOC in bytes. + pub total_size: u64, } fn number_of_bytes_to_fit(l: u64) -> usize { diff --git a/src/boc/serde.rs b/src/boc/serde.rs new file mode 100644 index 00000000..0df5f507 --- /dev/null +++ b/src/boc/serde.rs @@ -0,0 +1,418 @@ +use std::borrow::Cow; +use std::collections::{BTreeMap, HashMap}; +use std::hash::{BuildHasher, Hash}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::sync::Arc; + +use serde::de::{MapAccess, Visitor}; +use serde::ser::SerializeTuple; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::cell::{Cell, DynCell}; + +// === SerdeBoc === + +/// A wrapper type which implements `Serialize` and `Deserialize` for +/// types involving `Cell`. +#[repr(transparent)] +pub struct SerdeBoc(T); + +impl SerdeBoc { + /// Creates a wrapper around a reference to the value. + #[inline(always)] + pub const fn wrap(value: &T) -> &Self { + // SAFETY: SerdeBoc is #[repr(transparent)] + unsafe { &*(value as *const T as *const Self) } + } +} + +impl SerdeBoc { + #[inline(always)] + const fn wrap_slice(value: &[T]) -> &[Self] { + // SAFETY: SerdeBoc is #[repr(transparent)] + unsafe { std::slice::from_raw_parts(value.as_ptr() as *const Self, value.len()) } + } + + /// Consumes self, returning the inner value. + #[inline(always)] + pub fn into_inner(self) -> T { + self.0 + } +} + +impl From for SerdeBoc { + #[inline] + fn from(val: T) -> SerdeBoc { + Self(val) + } +} + +impl Serialize for SerdeBoc { + fn serialize(&self, serializer: S) -> Result { + self.0.serialize_as_boc(serializer) + } +} + +impl<'de, T: DeserializeAsBoc<'de>> Deserialize<'de> for SerdeBoc { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + T::deserialize_as_boc(deserializer).map(Self) + } +} + +// === Serializer stuff === + +trait SerializeAsBoc { + fn serialize_as_boc(&self, serializer: S) -> Result; +} + +impl SerializeAsBoc for Cell { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.as_ref().serialize(serializer) + } +} + +impl SerializeAsBoc for DynCell { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.serialize(serializer) + } +} + +impl SerializeAsBoc for &'_ T { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self, serializer) + } +} + +impl SerializeAsBoc for Option { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.as_ref().map(SerdeBoc::::wrap).serialize(serializer) + } +} + +impl SerializeAsBoc for Box { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self.as_ref(), serializer) + } +} + +impl SerializeAsBoc for Rc { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self.as_ref(), serializer) + } +} + +impl SerializeAsBoc for Arc { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self.as_ref(), serializer) + } +} + +impl SerializeAsBoc for [T] { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + SerdeBoc::wrap_slice(self).serialize(serializer) + } +} + +impl SerializeAsBoc for Vec { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.as_slice().serialize_as_boc(serializer) + } +} + +impl SerializeAsBoc for [T; 0] { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + ok!(serializer.serialize_tuple(0)).end() + } +} + +macro_rules! serialize_as_boc_array_impls { + ($($len:tt)+) => { + $( + #[allow(clippy::zero_prefixed_literal)] + impl SerializeAsBoc for [T; $len] { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = ok!(serializer.serialize_tuple($len)); + for e in self { + ok!(seq.serialize_element(SerdeBoc::::wrap(e))); + } + seq.end() + } + } + )+ + } +} + +serialize_as_boc_array_impls! { + 01 02 03 04 05 06 07 08 09 10 + 11 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 28 29 30 + 31 32 +} + +macro_rules! serialize_as_boc_map_impl { + ($ty:ident) => { + impl SerializeAsBoc for $ty + where + K: Serialize $(+ $kbound1 $(+ $kbound2)*)*, + V: SerializeAsBoc, + $($typaram: $bound,)* + { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_map(self.iter().map(|(k, v)| (k, SerdeBoc::wrap(v)))) + } + } + } +} + +serialize_as_boc_map_impl! { BTreeMap } +serialize_as_boc_map_impl! { HashMap } + +// === Deserializer stuff === + +trait DeserializeAsBoc<'de>: Sized { + fn deserialize_as_boc>(deserializer: D) -> Result; +} + +impl<'de> DeserializeAsBoc<'de> for Cell { + fn deserialize_as_boc>(deserializer: D) -> Result { + use serde::de::Error; + + let is_human_readable = deserializer.is_human_readable(); + let mut boc = ok!(borrow_cow_bytes(deserializer)); + + if is_human_readable { + match crate::util::decode_base64(boc) { + Ok(bytes) => { + boc = Cow::Owned(bytes); + } + Err(_) => return Err(Error::custom("invalid base64 string")), + } + } + + match crate::boc::Boc::decode(boc) { + Ok(cell) => Ok(cell), + Err(e) => Err(Error::custom(e)), + } + } +} + +impl<'de, T: DeserializeAsBoc<'de>> DeserializeAsBoc<'de> for Box { + #[inline] + fn deserialize_as_boc>(deserializer: D) -> Result { + let value = ok!(Box::>::deserialize(deserializer)); + // SAFETY: `SerdeBoc` has the same layout as `T`. + Ok(unsafe { Box::from_raw(Box::into_raw(value).cast::()) }) + } +} + +impl<'de, T: DeserializeAsBoc<'de>> DeserializeAsBoc<'de> for Option { + #[inline] + fn deserialize_as_boc>(deserializer: D) -> Result { + Ok(ok!(Option::>::deserialize(deserializer)).map(SerdeBoc::into_inner)) + } +} + +impl<'de, T: DeserializeAsBoc<'de>> DeserializeAsBoc<'de> for Vec { + #[inline] + fn deserialize_as_boc>(deserializer: D) -> Result { + Ok(ok!(Vec::>::deserialize(deserializer)) + .into_iter() + .map(SerdeBoc::into_inner) + .collect()) + } +} + +impl<'de, T: DeserializeAsBoc<'de>, const N: usize> DeserializeAsBoc<'de> for [T; N] +where + [SerdeBoc; N]: Deserialize<'de>, +{ + fn deserialize_as_boc>(deserializer: D) -> Result { + let value = ok!(<[SerdeBoc; N]>::deserialize(deserializer)); + Ok(value.map(SerdeBoc::into_inner)) + } +} + +macro_rules! deserialize_as_boc_map_impl { + ( + $ty:ident , + $access:ident, + $with_capacity:expr, + ) => { + impl<'de, K, V $(, $typaram)*> DeserializeAsBoc<'de> for $ty + where + K: Deserialize<'de> $(+ $kbound1 $(+ $kbound2)*)*, + V: DeserializeAsBoc<'de>, + $($typaram: $bound1 $(+ $bound2)*),* + { + fn deserialize_as_boc(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct MapVisitor { + marker: PhantomData<$ty>, + } + + impl<'de, K, V $(, $typaram)*> Visitor<'de> for MapVisitor + where + K: Deserialize<'de> $(+ $kbound1 $(+ $kbound2)*)*, + V: DeserializeAsBoc<'de>, + $($typaram: $bound1 $(+ $bound2)*),* + { + type Value = $ty; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map") + } + + #[inline] + fn visit_map(self, mut $access: A) -> Result + where + A: MapAccess<'de>, + { + let mut values = $with_capacity; + + while let Some((key, SerdeBoc(value))) = ok!($access.next_entry()) { + values.insert(key, value); + } + + Ok(values) + } + } + + let visitor = MapVisitor { marker: PhantomData }; + deserializer.deserialize_map(visitor) + } + } + } +} + +deserialize_as_boc_map_impl! { + BTreeMap, + map, + BTreeMap::new(), +} + +deserialize_as_boc_map_impl! { + HashMap, + map, + HashMap::with_capacity_and_hasher(cautious_size_hint::<(K, V)>(map.size_hint()), S::default()), +} + +fn cautious_size_hint(hint: Option) -> usize { + const MAX_PREALLOC_BYTES: usize = 1024 * 1024; + + if std::mem::size_of::() == 0 { + 0 + } else { + std::cmp::min( + hint.unwrap_or(0), + MAX_PREALLOC_BYTES / std::mem::size_of::(), + ) + } +} + +// === Other stuff === + +fn borrow_cow_bytes<'de: 'a, 'a, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + use serde::de::Error; + + struct CowBytesVisitor; + + impl<'a> Visitor<'a> for CowBytesVisitor { + type Value = Cow<'a, [u8]>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + Ok(Cow::Owned(v.as_bytes().to_vec())) + } + + fn visit_borrowed_str(self, v: &'a str) -> Result + where + E: Error, + { + Ok(Cow::Borrowed(v.as_bytes())) + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + Ok(Cow::Owned(v.into_bytes())) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + Ok(Cow::Owned(v.to_vec())) + } + + fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result + where + E: Error, + { + Ok(Cow::Borrowed(v)) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: Error, + { + Ok(Cow::Owned(v)) + } + } + + deserializer.deserialize_bytes(CowBytesVisitor) +} + +#[cfg(test)] +mod tests { + use crate::cell::CellFamily; + + use super::*; + + #[derive(serde::Serialize)] + struct StructWithDynCell<'a> { + #[serde(with = "crate::boc::Boc")] + cell: &'a DynCell, + } + + #[test] + fn serde_dyn_cell_works() { + serde_json::to_string(&StructWithDynCell { + cell: Cell::empty_cell_ref(), + }) + .unwrap(); + } +} diff --git a/src/cell/builder.rs b/src/cell/builder.rs index a4d4688f..e2af3e89 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -18,11 +18,8 @@ use super::CellTreeStats; /// A data structure that can be serialized into cells. pub trait Store { /// Tries to store itself into the cell builder. - fn store_into( - &self, - builder: &mut CellBuilder, - context: &mut dyn CellContext, - ) -> Result<(), Error>; + fn store_into(&self, builder: &mut CellBuilder, context: &dyn CellContext) + -> Result<(), Error>; } impl Store for &T { @@ -30,7 +27,7 @@ impl Store for &T { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ::store_into(self, builder, context) } @@ -41,7 +38,7 @@ impl Store for &mut T { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ::store_into(self, builder, context) } @@ -52,7 +49,7 @@ impl Store for Box { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ::store_into(self.as_ref(), builder, context) } @@ -63,7 +60,7 @@ impl Store for Arc { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ::store_into(self.as_ref(), builder, context) } @@ -74,7 +71,7 @@ impl Store for Rc { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ::store_into(self.as_ref(), builder, context) } @@ -82,7 +79,7 @@ impl Store for Rc { impl Store for () { #[inline] - fn store_into(&self, _: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, _: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { Ok(()) } } @@ -93,7 +90,7 @@ macro_rules! impl_store_for_tuples { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext + context: &dyn CellContext ) -> Result<(), Error> { let ($($field),+) = self; $(ok!($field.store_into(builder, context)));*; @@ -116,7 +113,7 @@ impl Store for Option { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Some(data) => { @@ -128,16 +125,16 @@ impl Store for Option { } } -impl<'a> Store for CellSlice<'a> { +impl Store for CellSlice<'_> { #[inline] - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_slice(self) } } impl Store for Cell { #[inline] - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_reference(self.clone()) } } @@ -148,7 +145,7 @@ macro_rules! impl_primitive_store { #[inline] fn store_into(&self, $b: &mut CellBuilder, - _: &mut dyn CellContext + _: &dyn CellContext ) -> Result<(), Error> { let $v = self; $expr @@ -297,19 +294,16 @@ impl CellBuilder { where T: Store, { - Self::build_from_ext(data, &mut Cell::empty_context()) + Self::build_from_ext(data, Cell::empty_context()) } /// Builds a new cell from the specified data using the provided cell context. #[inline] - pub fn build_from_ext(data: T, context: &mut dyn CellContext) -> Result + pub fn build_from_ext(data: T, context: &dyn CellContext) -> Result where T: Store, { - fn build_from_ext_impl( - data: &dyn Store, - context: &mut dyn CellContext, - ) -> Result { + fn build_from_ext_impl(data: &dyn Store, context: &dyn CellContext) -> Result { let mut builder = CellBuilder::new(); ok!(data.store_into(&mut builder, context)); builder.build_ext(context) @@ -340,16 +334,14 @@ impl CellBuilder { /// /// NOTE: intermediate cell hash is undefined. pub fn as_data_slice(&self) -> CellSlice<'_> { - // SAFETY: we interpret cell builder data as ordinary cell - unsafe { CellSlice::new_unchecked(IntermediateDataCell::wrap(self)) } + CellSlice::new_allow_pruned(IntermediateDataCell::wrap(self)) } /// Returns a slice which contains builder data and references. /// /// NOTE: intermediate cell hash is undefined. pub fn as_full_slice(&self) -> CellSlice<'_> { - // SAFETY: we interpret cell builder data as ordinary cell - unsafe { CellSlice::new_unchecked(IntermediateFullCell::wrap(self)) } + CellSlice::new_allow_pruned(IntermediateFullCell::wrap(self)) } /// Returns an underlying cell data. @@ -937,7 +929,7 @@ impl CellBuilder { } /// Tries to build a new cell using the specified cell context. - pub fn build_ext(mut self, context: &mut dyn CellContext) -> Result { + pub fn build_ext(mut self, context: &dyn CellContext) -> Result { debug_assert!(self.bit_len <= MAX_BIT_LEN); debug_assert!(self.references.len() <= MAX_REF_COUNT); @@ -1022,7 +1014,7 @@ impl CellBuilder { /// /// [`empty_context`]: fn@CellFamily::empty_context pub fn build(self) -> Result { - self.build_ext(&mut Cell::empty_context()) + self.build_ext(Cell::empty_context()) } /// Returns an object which will display data as a bitstring diff --git a/src/cell/cell_context.rs b/src/cell/cell_context.rs index ab28df33..0da3be98 100644 --- a/src/cell/cell_context.rs +++ b/src/cell/cell_context.rs @@ -10,14 +10,14 @@ use crate::cell::CellTreeStats; /// Gas accounting and resolcing exotic cells. pub trait CellContext { /// Builds a new cell from cell parts. - fn finalize_cell(&mut self, cell: CellParts<'_>) -> Result; + fn finalize_cell(&self, cell: CellParts<'_>) -> Result; /// Resolve an owned cell. - fn load_cell(&mut self, cell: Cell, mode: LoadMode) -> Result; + fn load_cell(&self, cell: Cell, mode: LoadMode) -> Result; /// Resolve a cell reference. - fn load_dyn_cell<'a>( - &mut self, + fn load_dyn_cell<'s: 'a, 'a>( + &'s self, cell: &'a DynCell, mode: LoadMode, ) -> Result<&'a DynCell, Error>; @@ -76,7 +76,7 @@ pub struct CellParts<'a> { pub data: &'a [u8], } -impl<'a> CellParts<'a> { +impl CellParts<'_> { /// Validates cell and computes all hashes. pub fn compute_hashes(&self) -> Result, Error> { const HASH_BITS: usize = 256; diff --git a/src/cell/cell_impl/mod.rs b/src/cell/cell_impl/mod.rs index cbd0c332..e58c62cd 100644 --- a/src/cell/cell_impl/mod.rs +++ b/src/cell/cell_impl/mod.rs @@ -23,19 +23,6 @@ macro_rules! define_gen_vtable_ptr { }; } -macro_rules! offset_of { - ($ty: path, $field: tt) => {{ - let $ty { $field: _, .. }; - - let uninit = ::std::mem::MaybeUninit::<$ty>::uninit(); - let base_ptr = uninit.as_ptr() as *const $ty; - unsafe { - let field_ptr = std::ptr::addr_of!((*base_ptr).$field); - (field_ptr as *const u8).offset_from(base_ptr as *const u8) as usize - } - }}; -} - /// Single-threaded cell implementation. #[cfg(not(feature = "sync"))] pub mod rc; diff --git a/src/cell/cell_impl/rc.rs b/src/cell/cell_impl/rc.rs index 4a6aaae1..8d0ab550 100644 --- a/src/cell/cell_impl/rc.rs +++ b/src/cell/cell_impl/rc.rs @@ -1,6 +1,7 @@ use std::alloc::Layout; use std::borrow::Borrow; -use std::rc::Rc; +use std::mem::offset_of; +use std::rc::{Rc, Weak}; use super::{ EmptyOrdinaryCell, HeaderWithData, LibraryReference, OrdinaryCell, OrdinaryCellHeader, @@ -25,6 +26,11 @@ impl Cell { pub fn untrack(self) -> Self { self.0.untrack() } + + /// Creates a new [`WeakCell`] reference to this cell. + pub fn downgrade(this: &Cell) -> WeakCell { + WeakCell(Rc::downgrade(&this.0)) + } } impl Default for Cell { @@ -107,8 +113,9 @@ impl CellFamily for Cell { } #[inline] - fn empty_context() -> Self::EmptyCellContext { - EmptyCellContext + fn empty_context() -> &'static Self::EmptyCellContext { + static EMPTY_CELL_CONTEXT: EmptyCellContext = EmptyCellContext; + &EMPTY_CELL_CONTEXT } #[inline] @@ -145,24 +152,44 @@ impl TryAsMut for Cell { } } +/// A non-owning reference to a [`Cell`]. +#[derive(Clone)] +#[repr(transparent)] +pub struct WeakCell(Weak); + +impl WeakCell { + /// Attempts to upgrade the `WeakCell` to a [`Cell`], + /// extending the lifetime of the data. + /// + /// Returns [`None`] if the inner value has since been dropped. + #[inline] + pub fn upgrade(&self) -> Option { + self.0.upgrade().map(Cell) + } +} + /// Empty context for single-threaded cells. #[derive(Debug, Default, Clone, Copy)] pub struct EmptyCellContext; impl CellContext for EmptyCellContext { - fn finalize_cell(&mut self, ctx: CellParts) -> Result { + fn finalize_cell(&self, ctx: CellParts) -> Result { let hashes = ok!(ctx.compute_hashes()); // SAFETY: ctx now represents a well-formed cell Ok(unsafe { make_cell(ctx, hashes) }) } #[inline] - fn load_cell(&mut self, cell: Cell, _: LoadMode) -> Result { + fn load_cell(&self, cell: Cell, _: LoadMode) -> Result { Ok(cell) } #[inline] - fn load_dyn_cell<'a>(&mut self, cell: &'a DynCell, _: LoadMode) -> Result<&'a DynCell, Error> { + fn load_dyn_cell<'s: 'a, 'a>( + &'s self, + cell: &'a DynCell, + _: LoadMode, + ) -> Result<&'a DynCell, Error> { Ok(cell) } } diff --git a/src/cell/cell_impl/sync.rs b/src/cell/cell_impl/sync.rs index 75babf82..3079071c 100644 --- a/src/cell/cell_impl/sync.rs +++ b/src/cell/cell_impl/sync.rs @@ -1,7 +1,8 @@ use std::alloc::Layout; use std::borrow::Borrow; +use std::mem::offset_of; use std::sync::atomic::AtomicUsize; -use std::sync::{Arc, OnceLock}; +use std::sync::{Arc, OnceLock, Weak}; use super::{ EmptyOrdinaryCell, HeaderWithData, LibraryReference, OrdinaryCell, OrdinaryCellHeader, @@ -26,6 +27,11 @@ impl Cell { pub fn untrack(self) -> Self { self.0.untrack() } + + /// Creates a new [`WeakCell`] reference to this cell. + pub fn downgrade(this: &Cell) -> WeakCell { + WeakCell(Arc::downgrade(&this.0)) + } } impl Default for Cell { @@ -108,8 +114,9 @@ impl CellFamily for Cell { } #[inline] - fn empty_context() -> Self::EmptyCellContext { - EmptyCellContext + fn empty_context() -> &'static Self::EmptyCellContext { + static EMPTY_CONTEXT: EmptyCellContext = EmptyCellContext; + &EMPTY_CONTEXT } #[inline] @@ -146,24 +153,44 @@ impl TryAsMut for Cell { } } +/// A non-owning reference to a [`Cell`]. +#[derive(Clone)] +#[repr(transparent)] +pub struct WeakCell(Weak); + +impl WeakCell { + /// Attempts to upgrade the `WeakCell` to a [`Cell`], + /// extending the lifetime of the data. + /// + /// Returns [`None`] if the inner value has since been dropped. + #[inline] + pub fn upgrade(&self) -> Option { + self.0.upgrade().map(Cell) + } +} + /// Empty context for thread-safe cells. #[derive(Debug, Default, Clone, Copy)] pub struct EmptyCellContext; impl CellContext for EmptyCellContext { - fn finalize_cell(&mut self, ctx: CellParts) -> Result { + fn finalize_cell(&self, ctx: CellParts) -> Result { let hashes = ok!(ctx.compute_hashes()); // SAFETY: ctx now represents a well-formed cell Ok(unsafe { make_cell(ctx, hashes) }) } #[inline] - fn load_cell(&mut self, cell: Cell, _: LoadMode) -> Result { + fn load_cell(&self, cell: Cell, _: LoadMode) -> Result { Ok(cell) } #[inline] - fn load_dyn_cell<'a>(&mut self, cell: &'a DynCell, _: LoadMode) -> Result<&'a DynCell, Error> { + fn load_dyn_cell<'s: 'a, 'a>( + &'s self, + cell: &'a DynCell, + _: LoadMode, + ) -> Result<&'a DynCell, Error> { Ok(cell) } } diff --git a/src/cell/mod.rs b/src/cell/mod.rs index 3f1a1ab7..96acbf9a 100644 --- a/src/cell/mod.rs +++ b/src/cell/mod.rs @@ -13,10 +13,10 @@ pub use self::slice::{CellSlice, CellSliceParts, CellSliceRange, ExactSize, Load pub use self::usage_tree::{UsageTree, UsageTreeMode, UsageTreeWithSubtrees}; #[cfg(not(feature = "sync"))] -pub use self::cell_impl::rc::{Cell, CellInner}; +pub use self::cell_impl::rc::{Cell, CellInner, WeakCell}; #[cfg(feature = "sync")] -pub use self::cell_impl::sync::{Cell, CellInner}; +pub use self::cell_impl::sync::{Cell, CellInner, WeakCell}; pub use everscale_types_proc::{Load, Store}; @@ -64,7 +64,7 @@ pub trait CellFamily: Sized { fn empty_cell_ref() -> &'static DynCell; /// Creates an empty cell context. - fn empty_context() -> Self::EmptyCellContext; + fn empty_context() -> &'static Self::EmptyCellContext; /// Returns a static reference to the cell with all zeros. fn all_zeros_ref() -> &'static DynCell; @@ -241,6 +241,14 @@ impl DynCell { CellSlice::new(self) } + /// Returns this cell as a cell slice. + /// + /// Loads cell as is. + #[inline] + pub fn as_slice_allow_pruned(&'_ self) -> CellSlice<'_> { + CellSlice::new_allow_pruned(self) + } + /// Returns this cell as a cell slice. /// /// # Safety @@ -248,8 +256,9 @@ impl DynCell { /// The following must be true: /// - cell is not pruned #[inline] + #[deprecated = "use `{Self}::as_slice_allow_pruned` instead"] pub unsafe fn as_slice_unchecked(&'_ self) -> CellSlice<'_> { - CellSlice::new_unchecked(self) + CellSlice::new_allow_pruned(self) } /// Recursively computes the count of distinct cells returning @@ -259,6 +268,14 @@ impl DynCell { StorageStat::compute_for_cell(self, limit) } + /// Recursively traverses the cells tree without tracking a uniqueness + /// of cells. Usefull for adding small subtrees to merkle proofs. + pub fn touch_recursive(&self) { + for child in self.references() { + child.touch_recursive(); + } + } + /// Returns an object that implements [`Debug`] for printing only /// the root cell of the cell tree. /// @@ -459,7 +476,7 @@ impl<'a> Iterator for RefsIter<'a> { } } -impl<'a> DoubleEndedIterator for RefsIter<'a> { +impl DoubleEndedIterator for RefsIter<'_> { #[inline] fn next_back(&mut self) -> Option { if self.max > self.index { @@ -513,7 +530,7 @@ impl Clone for ClonedRefsIter<'_> { } } -impl<'a> Iterator for ClonedRefsIter<'a> { +impl Iterator for ClonedRefsIter<'_> { type Item = Cell; #[inline] @@ -533,7 +550,7 @@ impl<'a> Iterator for ClonedRefsIter<'a> { } } -impl<'a> DoubleEndedIterator for ClonedRefsIter<'a> { +impl DoubleEndedIterator for ClonedRefsIter<'_> { #[inline] fn next_back(&mut self) -> Option { if self.inner.max > self.inner.index { @@ -832,7 +849,7 @@ impl<'de> serde::Deserialize<'de> for HashBytes { struct HashBytesHexVisitor; - impl<'de> Visitor<'de> for HashBytesHexVisitor { + impl Visitor<'_> for HashBytesHexVisitor { type Value = HashBytes; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -853,7 +870,7 @@ impl<'de> serde::Deserialize<'de> for HashBytes { pub struct HashBytesRawVisitor; - impl<'de> Visitor<'de> for HashBytesRawVisitor { + impl Visitor<'_> for HashBytesRawVisitor { type Value = HashBytes; fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -912,12 +929,18 @@ impl CellType { !matches!(self, Self::Ordinary) } - /// Returns whether the cell is a pruned branch + /// Returns whether the cell is a pruned branch. #[inline] pub const fn is_pruned_branch(self) -> bool { matches!(self, Self::PrunedBranch) } + /// Returns whether this cell is a library cell. + #[inline(always)] + pub const fn is_library(self) -> bool { + matches!(self, Self::LibraryReference) + } + /// Encodes cell type as byte. #[inline] pub const fn to_byte(self) -> u8 { @@ -1004,6 +1027,14 @@ impl CellDescriptor { } } + /// Creates a new descriptor with a new mask, shifted by the offset. + #[must_use] + pub const fn virtualize(mut self, offset: u8) -> Self { + let virtualized_mask = (self.d1 >> offset) & Self::LEVEL_MASK; + self.d1 = virtualized_mask | (self.d1 & !Self::LEVEL_MASK); + self + } + /// Computes cell type. pub fn cell_type(self) -> CellType { if self.d1 & Self::IS_EXOTIC_MASK == 0 { @@ -1051,12 +1082,18 @@ impl CellDescriptor { self.d1 & Self::IS_EXOTIC_MASK != 0 } - /// Returns whether this cell is a pruned branch cell + /// Returns whether this cell is a pruned branch cell. #[inline(always)] pub const fn is_pruned_branch(self) -> bool { self.is_exotic() && self.reference_count() == 0 && !self.level_mask().is_empty() } + /// Returns whether this cell is a library cell. + #[inline(always)] + pub const fn is_library(self) -> bool { + self.is_exotic() && self.reference_count() == 0 && self.level_mask().is_empty() + } + /// Returns whether this cell type is Merkle proof or Merkle update. #[inline(always)] pub const fn is_merkle(self) -> bool { @@ -1153,11 +1190,12 @@ impl LevelMask { /// Returns whether the specified level is included into the mask. pub const fn contains(self, level: u8) -> bool { - level == 0 || self.0 & LevelMask::from_level(level).0 != 0 + level == 0 || self.0 & (1 << (level - 1)) != 0 } /// Creates a new mask, shifted by the offset. #[inline(always)] + #[must_use] pub const fn virtualize(self, offset: u8) -> Self { Self(self.0 >> offset) } @@ -1643,6 +1681,30 @@ mod tests { } } + #[test] + fn virtualize_descriptor() { + let level_mask = LevelMask(0b111); + let desc = CellDescriptor::new([ + CellDescriptor::compute_d1(level_mask, false, 3), + CellDescriptor::compute_d2(123), + ]); + + assert_eq!(desc.level_mask(), level_mask); + + for i in 0..3 { + let v_desc = desc.virtualize(i); + + assert_eq!(v_desc.cell_type(), desc.cell_type()); + assert_eq!(v_desc.reference_count(), desc.reference_count()); + assert_eq!(v_desc.is_exotic(), desc.is_exotic()); + assert_eq!(v_desc.store_hashes(), desc.store_hashes()); + assert_eq!(v_desc.is_aligned(), desc.is_aligned()); + assert_eq!(v_desc.byte_len(), desc.byte_len()); + + assert_eq!(v_desc.level_mask(), level_mask.virtualize(i)); + } + } + #[test] fn correct_hash_index() { const HASH_INDEX_TABLE: [[u8; 4]; 8] = [ @@ -1672,16 +1734,13 @@ mod tests { let cell = Cell::empty_cell(); let pruned1 = - crate::merkle::make_pruned_branch(cell.as_ref(), 0, &mut Cell::empty_context()) - .unwrap(); + crate::merkle::make_pruned_branch(cell.as_ref(), 0, Cell::empty_context()).unwrap(); let pruned2 = - crate::merkle::make_pruned_branch(pruned1.as_ref(), 1, &mut Cell::empty_context()) - .unwrap(); + crate::merkle::make_pruned_branch(pruned1.as_ref(), 1, Cell::empty_context()).unwrap(); let pruned3 = - crate::merkle::make_pruned_branch(pruned2.as_ref(), 2, &mut Cell::empty_context()) - .unwrap(); + crate::merkle::make_pruned_branch(pruned2.as_ref(), 2, Cell::empty_context()).unwrap(); // Level 3 -> 2 let pruned3 = pruned3.virtualize(); @@ -1708,4 +1767,84 @@ mod tests { assert_eq!(pruned3.repr_hash(), cell.repr_hash()); assert_eq!(pruned3.repr_depth(), cell.repr_depth()); } + + #[test] + fn versioned_store_load() { + #[derive(Debug, Clone, Copy, Eq, PartialEq, Store, Load)] + #[tlb(tag = ["#12", "#34", "#56"])] + struct Versioned { + old_field1: u32, + #[tlb(since_tag = 1)] + new_field1: u32, + old_field2: bool, + #[tlb(since_tag = 2)] + new_field2: bool, + } + + let old = Versioned { + old_field1: 123, + new_field1: 0, + old_field2: true, + new_field2: false, + }; + let cell = CellBuilder::build_from(old).unwrap(); + + { + let mut slice = cell.as_slice().unwrap(); + assert_eq!(slice.size_bits(), 8 + 32 + 1); + assert_eq!(slice.load_u8().unwrap(), 0x12); + assert_eq!(slice.load_u32().unwrap(), 123); + assert!(slice.load_bit().unwrap()); + assert!(slice.is_empty()); + } + assert_eq!( + Versioned::load_from(&mut cell.as_slice().unwrap()).unwrap(), + old, + ); + + let new = Versioned { + old_field1: 123, + new_field1: 456, + old_field2: true, + new_field2: false, + }; + let cell = CellBuilder::build_from(new).unwrap(); + + { + let mut slice = cell.as_slice().unwrap(); + assert_eq!(slice.size_bits(), 8 + 32 + 32 + 1); + assert_eq!(slice.load_u8().unwrap(), 0x34); + assert_eq!(slice.load_u32().unwrap(), 123); + assert_eq!(slice.load_u32().unwrap(), 456); + assert!(slice.load_bit().unwrap()); + assert!(slice.is_empty()); + } + assert_eq!( + Versioned::load_from(&mut cell.as_slice().unwrap()).unwrap(), + new + ); + + let too_new = Versioned { + old_field1: 123, + new_field1: 0, + old_field2: true, + new_field2: true, + }; + let cell = CellBuilder::build_from(too_new).unwrap(); + + { + let mut slice = cell.as_slice().unwrap(); + assert_eq!(slice.size_bits(), 8 + 32 + 32 + 1 + 1); + assert_eq!(slice.load_u8().unwrap(), 0x56); + assert_eq!(slice.load_u32().unwrap(), 123); + assert_eq!(slice.load_u32().unwrap(), 0); + assert!(slice.load_bit().unwrap()); + assert!(slice.load_bit().unwrap()); + assert!(slice.is_empty()); + } + assert_eq!( + Versioned::load_from(&mut cell.as_slice().unwrap()).unwrap(), + too_new + ); + } } diff --git a/src/cell/slice.rs b/src/cell/slice.rs index b634b304..a2cea3e1 100644 --- a/src/cell/slice.rs +++ b/src/cell/slice.rs @@ -390,6 +390,41 @@ impl CellSliceRange { Ok(()) } + /// Returns the first `bits` and `refs` of the slice and advances the start + /// of data and refs windows. + #[must_use = "use `skip_first` if you don't need the result"] + pub fn split_prefix(&mut self, bits: u16, refs: u8) -> Result { + if unlikely( + self.bits_start + bits > self.bits_end || self.refs_start + refs > self.refs_end, + ) { + return Err(Error::CellUnderflow); + } + + let mut res = *self; + self.bits_start += bits; + self.refs_start += refs; + res.bits_end = self.bits_start; + res.refs_end = self.refs_start; + Ok(res) + } + + /// Returns the last `bits` and `refs` of the slice and shrinks the data and refs windows. + #[must_use = "use `skip_last` if you don't need the result"] + pub fn split_suffix(&mut self, bits: u16, refs: u8) -> Result { + if unlikely( + self.bits_start + bits > self.bits_end || self.refs_start + refs > self.refs_end, + ) { + return Err(Error::CellUnderflow); + } + + let mut res = *self; + self.bits_end -= bits; + self.refs_end -= refs; + res.bits_start = self.bits_end; + res.refs_start = self.refs_end; + Ok(res) + } + /// Returns a slice range starting at the same bits and refs offsets, /// and containing no more than `bits` of data and `refs` of children. pub fn get_prefix(&self, bits: u16, refs: u8) -> Self { @@ -421,8 +456,7 @@ pub struct CellSlice<'a> { impl Default for CellSlice<'_> { #[inline] fn default() -> Self { - // SAFETY: empty cell is an ordinary cell - unsafe { Cell::empty_cell_ref().as_slice_unchecked() } + Cell::empty_cell_ref().as_slice_allow_pruned() } } @@ -456,12 +490,7 @@ impl<'a> CellSlice<'a> { } /// Constructs a new cell slice from the specified cell. - /// - /// # Safety - /// - /// The following must be true: - /// - cell is not pruned - pub unsafe fn new_unchecked(cell: &'a DynCell) -> Self { + pub fn new_allow_pruned(cell: &'a DynCell) -> Self { Self { range: CellSliceRange::full(cell), cell, @@ -759,7 +788,7 @@ impl<'a> CellSlice<'a> { return Ok(false); } - let mut other = other.clone(); + let mut other = *other; let ok = other.only_first(bits, 0).is_ok(); debug_assert!(ok); @@ -773,7 +802,7 @@ impl<'a> CellSlice<'a> { return Ok(false); } - let mut other = other.clone(); + let mut other = *other; let ok = other.only_last(bits, 0).is_ok(); debug_assert!(ok); @@ -942,6 +971,42 @@ impl<'a> CellSlice<'a> { std::cmp::min(prefix_len, max_bit_len) } + /// Returns the number of leading bits of `self`. + pub fn count_leading(&self, bit: bool) -> Result { + if self.range.bits_start >= self.range.bits_end { + return Ok(0); + } + let data = self.cell.data(); + + // Check if data is enough + if (self.range.bits_end + 7) / 8 > data.len() as u16 { + return Err(Error::CellUnderflow); + } + + let bit_count = self.range.bits_end - self.range.bits_start; + + // SAFETY: `bits_end` is in data range + Ok(unsafe { bits_memscan(data, self.range.bits_start, bit_count, bit) }) + } + + /// Returns the number of trailing bits of `self`. + pub fn count_trailing(&self, bit: bool) -> Result { + if self.range.bits_start >= self.range.bits_end { + return Ok(0); + } + let data = self.cell.data(); + + // Check if data is enough + if (self.range.bits_end + 7) / 8 > data.len() as u16 { + return Err(Error::CellUnderflow); + } + + let bit_count = self.range.bits_end - self.range.bits_start; + + // SAFETY: `bits_end` is in data range + Ok(unsafe { bits_memscan_rev(data, self.range.bits_start, bit_count, bit) }) + } + /// Checks whether the current slice consists of the same bits, /// returns `None` if there are 0s and 1s, returns `Some(bit)` otherwise. /// @@ -976,7 +1041,6 @@ impl<'a> CellSlice<'a> { if self.range.bits_start >= self.range.bits_end { return None; } - let mut remaining_bits = self.range.bits_end - self.range.bits_start; let data = self.cell.data(); // Check if data is enough @@ -984,18 +1048,22 @@ impl<'a> CellSlice<'a> { return None; } - let r = self.range.bits_start % 8; - let q = (self.range.bits_start / 8) as usize; + let mut bit_count = self.range.bits_end - self.range.bits_start; - unsafe { - let mut data_ptr = data.as_ptr().add(q); - let first_byte = *data_ptr; + let r = self.range.bits_start & 0b111; + let q = self.range.bits_start >> 3; + + // SAFETY: q is in data range + let mut data_ptr = unsafe { data.as_ptr().add(q as usize) }; + let first_byte = unsafe { *data_ptr }; + let bit = (first_byte >> (7 - r)) & 1 != 0; - let target = ((first_byte >> (7 - r)) & 1) * u8::MAX; + if bit_count < 64 { + let target = (bit as u8) * u8::MAX; let first_byte_mask: u8 = 0xff >> r; - let last_byte_mask: u8 = 0xff << ((8 - (remaining_bits + r) % 8) % 8); + let last_byte_mask: u8 = 0xff << ((8 - (bit_count + r) % 8) % 8); - if r + remaining_bits <= 8 { + if r + bit_count <= 8 { // Special case if all remaining_bits are in the first byte if ((first_byte ^ target) & first_byte_mask & last_byte_mask) != 0 { return None; @@ -1006,25 +1074,31 @@ impl<'a> CellSlice<'a> { return None; } - // Check all full bytes - remaining_bits -= 8 - r; - for _ in 0..(remaining_bits / 8) { - data_ptr = data_ptr.add(1); - if *data_ptr != target { - return None; + unsafe { + // Check all full bytes + bit_count -= 8 - r; + for _ in 0..(bit_count / 8) { + data_ptr = data_ptr.add(1); + if *data_ptr != target { + return None; + } } - } - // Check the last byte (if not aligned) - if remaining_bits % 8 != 0 { - data_ptr = data_ptr.add(1); - if (*data_ptr ^ target) & last_byte_mask != 0 { - return None; + // Check the last byte (if not aligned) + if bit_count % 8 != 0 { + data_ptr = data_ptr.add(1); + if (*data_ptr ^ target) & last_byte_mask != 0 { + return None; + } } } } - Some(target != 0) + Some(bit) + } else { + // SAFETY: `bits_end` is in data range + let same_bits = unsafe { bits_memscan(data, self.range.bits_start, bit_count, bit) }; + (same_bits == bit_count).then_some(bit) } } @@ -1515,6 +1589,27 @@ impl<'a> CellSlice<'a> { result } + /// Returns the first `bits` and `refs` of the slice and advances the start + /// of data and refs windows. + #[must_use = "use `skip_first` if you don't need the result"] + pub fn load_prefix(&mut self, bits: u16, refs: u8) -> Result { + let prefix_range = ok!(self.range.split_prefix(bits, refs)); + Ok(Self { + cell: self.cell, + range: prefix_range, + }) + } + + /// Returns the last `bits` and `refs` of the slice and shrinks the data and refs windows. + #[must_use = "use `skip_last` if you don't need the result"] + pub fn load_suffix(&mut self, bits: u16, refs: u8) -> Result { + let suffix_range = ok!(self.range.split_suffix(bits, refs)); + Ok(Self { + cell: self.cell, + range: suffix_range, + }) + } + /// Returns a reference to the Nth child cell (relative to this slice's refs window). pub fn get_reference(&self, index: u8) -> Result<&'a DynCell, Error> { if self.range.refs_start + index < self.range.refs_end { @@ -1733,6 +1828,149 @@ impl_exact_size_for_tuples! { (0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5), } +/// # Safety +/// The following must be true: +/// - (offset + bit_count + 7) / 8 <= data.len() +unsafe fn bits_memscan(data: &[u8], mut offset: u16, bit_count: u16, cmp_to: bool) -> u16 { + #[inline] + fn is_aligned_to_u64(ptr: *const u8) -> bool { + // SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the + // provenance). + let addr = ptr.cast::<()>() as usize; + addr & (std::mem::align_of::() - 1) == 0 + } + + if bit_count == 0 { + return 0; + } + debug_assert!((offset + bit_count + 7) as usize / 8 <= data.len()); + + let xor_value = cmp_to as u8 * u8::MAX; + + // Apply offset to byte level + let mut ptr = data.as_ptr().add(offset as usize >> 3); + offset &= 0b111; + + let mut rem = bit_count; + if offset > 0 { + // NOTE: `offset` is in range 1..=7 + let v = (*ptr ^ xor_value) << offset; + let c = v.leading_zeros() as u16; + let l = 8 - offset; + if c < l || bit_count <= l { + return c.min(bit_count); + } + + ptr = ptr.add(1); + rem -= l; + } + + while rem >= 8 && !is_aligned_to_u64(ptr) { + let v = *ptr ^ xor_value; + if v > 0 { + return bit_count - rem + v.leading_zeros() as u16; + } + + ptr = ptr.add(1); + rem -= 8; + } + + let xor_value_l = cmp_to as u64 * u64::MAX; + while rem >= 64 { + #[cfg(target_endian = "little")] + let z = { (*ptr.cast::()).swap_bytes() ^ xor_value_l }; + #[cfg(not(target_endian = "little"))] + let z = { *ptr.cast::() ^ xor_value_l }; + + if z > 0 { + return bit_count - rem + z.leading_zeros() as u16; + } + + ptr = ptr.add(8); + rem -= 64; + } + + while rem >= 8 { + let v = *ptr ^ xor_value; + if v > 0 { + return bit_count - rem + v.leading_zeros() as u16; + } + + ptr = ptr.add(1); + rem -= 8; + } + + if rem > 0 { + let v = *ptr ^ xor_value; + let c = v.leading_zeros() as u16; + if c < rem { + return bit_count - rem + c; + } + } + + bit_count +} + +// # Safety +/// The following must be true: +/// - (offset + bit_count + 7) / 8 <= data.len() +unsafe fn bits_memscan_rev(data: &[u8], mut offset: u16, mut bit_count: u16, cmp_to: bool) -> u16 { + if bit_count == 0 { + return 0; + } + debug_assert!((offset + bit_count + 7) as usize / 8 <= data.len()); + + let xor_value = cmp_to as u8 * u8::MAX; + let mut ptr = data.as_ptr().add((offset + bit_count) as usize >> 3); + offset = (offset + bit_count) & 0b111; + + let mut res = offset; + if offset > 0 { + let v = (*ptr >> (8 - offset)) ^ xor_value; + let c = v.trailing_zeros() as u16; + if c < offset || res >= bit_count { + return c.min(bit_count); + } + bit_count -= res; + } + + let xor_value_l = cmp_to as u32 * u32::MAX; + while bit_count >= 32 { + ptr = ptr.sub(4); + + #[cfg(target_endian = "little")] + let v = { ptr.cast::().read_unaligned().swap_bytes() ^ xor_value_l }; + #[cfg(not(target_endian = "little"))] + let v = { ptr.cast::().read_unaligned() ^ xor_value_l }; + + if v > 0 { + return res + v.trailing_zeros() as u16; + } + + res += 32; + bit_count -= 32; + } + + while bit_count >= 8 { + ptr = ptr.sub(1); + let v = *ptr ^ xor_value; + if v > 0 { + return res + v.trailing_zeros() as u16; + } + + res += 8; + bit_count -= 8; + } + + if bit_count > 0 { + ptr = ptr.sub(1); + let v = *ptr ^ xor_value; + res + std::cmp::min(v.trailing_zeros() as u16, bit_count) + } else { + res + } +} + #[cfg(test)] mod tests { use crate::error::Error; @@ -2085,4 +2323,159 @@ mod tests { Ok(()) } + + #[test] + fn leading_bits() -> anyhow::Result<()> { + // Empty slice has zero leading bits + assert_eq!(Cell::empty_cell_ref().as_slice()?.count_leading(false)?, 0); + assert_eq!(Cell::empty_cell_ref().as_slice()?.count_leading(true)?, 0); + + // Full slice has all bits set + assert_eq!( + Cell::all_zeros_ref().as_slice()?.count_leading(false)?, + 1023 + ); + assert_eq!(Cell::all_ones_ref().as_slice()?.count_leading(true)?, 1023); + + // Full slice has no leading other bits + assert_eq!(Cell::all_zeros_ref().as_slice()?.count_leading(true)?, 0); + assert_eq!(Cell::all_ones_ref().as_slice()?.count_leading(false)?, 0); + + // Test for different alignments + for shift_before in [false, true] { + for shift_after in [false, true] { + for i in 0..128 { + let mut builder = CellBuilder::new(); + + if shift_before { + builder.store_ones(7)?; + }; + builder.store_u128(1 << i)?; + if shift_after { + builder.store_ones(14)?; + } + + let mut slice = builder.as_data_slice(); + + if shift_before { + slice.skip_first(7, 0)?; + } + if shift_after { + slice.only_first(128, 0)?; + } + + assert_eq!(slice.count_leading(false)?, 127 - i); + assert_eq!(slice.count_leading(true)?, (i == 127) as u16); + } + } + } + + Ok(()) + } + + #[test] + fn trailing_bits() -> anyhow::Result<()> { + // Empty slice has zero trailing bits + assert_eq!(Cell::empty_cell_ref().as_slice()?.count_trailing(false)?, 0); + assert_eq!(Cell::empty_cell_ref().as_slice()?.count_trailing(true)?, 0); + + // Full slice has all bits set + assert_eq!( + Cell::all_zeros_ref().as_slice()?.count_trailing(false)?, + 1023 + ); + assert_eq!(Cell::all_ones_ref().as_slice()?.count_trailing(true)?, 1023); + + // Full slice has no trailing other bits + assert_eq!(Cell::all_zeros_ref().as_slice()?.count_trailing(true)?, 0); + assert_eq!(Cell::all_ones_ref().as_slice()?.count_trailing(false)?, 0); + + // Test for different alignments + for shift_before in [false, true] { + for shift_after in [false, true] { + for i in 0..128 { + let mut builder = CellBuilder::new(); + + if shift_before { + builder.store_ones(7)?; + }; + builder.store_u128(1 << i)?; + if shift_after { + builder.store_ones(14)?; + } + + let mut slice = builder.as_data_slice(); + + if shift_before { + slice.skip_first(7, 0)?; + } + if shift_after { + slice.only_first(128, 0)?; + } + + assert_eq!(slice.count_trailing(false)?, i); + assert_eq!(slice.count_trailing(true)?, (i == 0) as u16); + } + } + } + + Ok(()) + } + + #[test] + fn split_slice() -> anyhow::Result<()> { + let cell = CellBuilder::build_from((0xdeafbeafu32, 0xabbacafeu32))?; + + // Prefix + { + let mut cs = cell.as_slice()?; + assert!(cs.load_prefix(0, 1).is_err()); + + let mut prefix = cs.load_prefix(16, 0)?; + assert_eq!(prefix.size_bits(), 16); + assert_eq!(cs.size_bits(), 64 - 16); + assert_eq!(prefix.load_u16()?, 0xdeaf); + assert_eq!(cs.get_u16(0)?, 0xbeaf); + + let mut prefix = cs.load_prefix(32, 0)?; + assert_eq!(prefix.size_bits(), 32); + assert_eq!(cs.size_bits(), 64 - 16 - 32); + assert_eq!(prefix.load_u32()?, 0xbeafabba); + assert_eq!(cs.get_u16(0)?, 0xcafe); + + let mut prefix = cs.load_prefix(16, 0)?; + assert_eq!(prefix.size_bits(), 16); + assert_eq!(cs.size_bits(), 0); + assert_eq!(prefix.load_u16()?, 0xcafe); + + assert!(cs.load_prefix(10, 0).is_err()); + } + + // Suffix + { + let mut cs = cell.as_slice()?; + assert!(cs.load_suffix(0, 1).is_err()); + + let mut suffix = cs.load_suffix(16, 0)?; + assert_eq!(suffix.size_bits(), 16); + assert_eq!(cs.size_bits(), 64 - 16); + assert_eq!(suffix.load_u16()?, 0xcafe); + assert_eq!(cs.get_u16(32)?, 0xabba); + + let mut suffix = cs.load_suffix(32, 0)?; + assert_eq!(suffix.size_bits(), 32); + assert_eq!(cs.size_bits(), 64 - 16 - 32); + assert_eq!(suffix.load_u32()?, 0xbeafabba); + assert_eq!(cs.get_u16(0)?, 0xdeaf); + + let mut suffix = cs.load_suffix(16, 0)?; + assert_eq!(suffix.size_bits(), 16); + assert_eq!(cs.size_bits(), 0); + assert_eq!(suffix.load_u16()?, 0xdeaf); + + assert!(cs.load_suffix(10, 0).is_err()); + } + + Ok(()) + } } diff --git a/src/dict/aug.rs b/src/dict/aug.rs index 06385ab5..3d85e072 100644 --- a/src/dict/aug.rs +++ b/src/dict/aug.rs @@ -2,7 +2,10 @@ use std::borrow::Borrow; use std::collections::BTreeMap; use std::marker::PhantomData; -use super::{aug_dict_insert, aug_dict_remove_owned, build_aug_dict_from_sorted_iter, SetMode}; +use super::{ + aug_dict_find_by_extra, aug_dict_insert, aug_dict_remove_owned, + build_aug_dict_from_sorted_iter, SearchByExtra, SetMode, +}; use crate::cell::*; use crate::error::*; use crate::util::*; @@ -24,7 +27,7 @@ pub trait AugDictExtra: Default { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - cx: &mut dyn CellContext, + cx: &dyn CellContext, ) -> Result<(), Error>; } @@ -75,7 +78,7 @@ impl Store for AugDict { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ok!(self.dict.store_into(builder, context)); self.extra.store_into(builder, context) @@ -160,7 +163,7 @@ impl AugDict { #[allow(unused)] pub(crate) fn load_from_root<'a>( slice: &mut CellSlice<'a>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where A: Load<'a>, @@ -202,7 +205,7 @@ where fn load_from_root<'a, A, V>( slice: &mut CellSlice<'a>, key_bit_len: u16, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(A, Cell), Error> where A: Load<'a>, @@ -277,6 +280,35 @@ where } } +impl AugDict +where + K: DictKey, +{ + /// Searches for an item using a predicate on extra values. + /// + /// Used as a secondary index. + pub fn find_by_extra<'a, S>(&'a self, flow: S) -> Result, Error> + where + S: SearchByExtra, + A: Load<'a>, + V: Load<'a>, + { + let Some((key, extra, mut value)) = ok!(aug_dict_find_by_extra::( + self.dict.root.as_ref(), + K::BITS, + flow + )) else { + return Ok(None); + }; + + let Some(key) = K::from_raw_data(key.raw_data()) else { + return Err(Error::CellUnderflow); + }; + let value = ok!(V::load_from(&mut value)); + Ok(Some((key, extra, value))) + } +} + impl AugDict where K: Store + DictKey, @@ -297,7 +329,7 @@ where .map(|(k, (a, v))| (k.borrow(), a.borrow(), v.borrow())), K::BITS, A::comp_add, - &mut Cell::empty_context() + Cell::empty_context() )); let mut result = Self { @@ -324,7 +356,7 @@ where .map(|(k, a, v)| (k.borrow(), a.borrow(), v.borrow())), K::BITS, A::comp_add, - &mut Cell::empty_context() + Cell::empty_context() )); let mut result = Self { @@ -348,7 +380,7 @@ where E: Borrow, T: Borrow, { - self.set_ext(key, aug, value, &mut Cell::empty_context()) + self.set_ext(key, aug, value, Cell::empty_context()) } /// Sets the value associated with the key in the dictionary. @@ -357,7 +389,7 @@ where key: Q, aug: E, value: T, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where Q: Borrow, @@ -385,7 +417,7 @@ where E: Borrow, T: Borrow, { - self.replace_ext(key, aug, value, &mut Cell::empty_context()) + self.replace_ext(key, aug, value, Cell::empty_context()) } /// Sets the value associated with the key in the dictionary @@ -395,7 +427,7 @@ where key: Q, aug: E, value: T, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where Q: Borrow, @@ -423,7 +455,7 @@ where E: Borrow, T: Borrow, { - self.add_ext(key, aug, value, &mut Cell::empty_context()) + self.add_ext(key, aug, value, Cell::empty_context()) } /// Sets the value associated with key in dictionary, @@ -433,7 +465,7 @@ where key: Q, aug: E, value: T, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where Q: Borrow, @@ -457,7 +489,7 @@ where for<'a> A: Load<'a> + 'static, for<'a> V: Load<'a> + 'static, { - match ok!(self.remove_raw_ext(key, &mut Cell::empty_context())) { + match ok!(self.remove_raw_ext(key, Cell::empty_context())) { Some((cell, range)) => { let mut slice = ok!(range.apply(&cell)); let extra = ok!(A::load_from(&mut slice)); @@ -473,7 +505,7 @@ where pub fn remove_raw_ext( &mut self, key: Q, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> where Q: Borrow, @@ -487,10 +519,10 @@ where extra: &A, value: &V, mode: SetMode, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { let mut key_builder = CellBuilder::new(); - ok!(key.store_into(&mut key_builder, &mut Cell::empty_context())); + ok!(key.store_into(&mut key_builder, Cell::empty_context())); let inserted = ok!(aug_dict_insert( &mut self.dict.root, &mut key_builder.as_data_slice(), @@ -512,10 +544,10 @@ where fn remove_impl( &mut self, key: &K, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { let mut key_builder = CellBuilder::new(); - ok!(key.store_into(&mut key_builder, &mut Cell::empty_context())); + ok!(key.store_into(&mut key_builder, Cell::empty_context())); let res = ok!(aug_dict_remove_owned( &mut self.dict.root, &mut key_builder.as_data_slice(), @@ -534,24 +566,24 @@ where /// Split dictionary into 2 dictionaries by the first key bit. pub fn split(&self) -> Result<(Self, Self), Error> { - self.split_by_prefix_ext(&Default::default(), &mut Cell::empty_context()) + self.split_by_prefix_ext(&Default::default(), Cell::empty_context()) } /// Split dictionary into 2 dictionaries by the first key bit. - pub fn split_ext(&self, context: &mut dyn CellContext) -> Result<(Self, Self), Error> { + pub fn split_ext(&self, context: &dyn CellContext) -> Result<(Self, Self), Error> { self.split_by_prefix_ext(&Default::default(), context) } /// Split dictionary into 2 dictionaries at the prefix. pub fn split_by_prefix(&self, key_prefix: &CellSlice<'_>) -> Result<(Self, Self), Error> { - self.split_by_prefix_ext(key_prefix, &mut Cell::empty_context()) + self.split_by_prefix_ext(key_prefix, Cell::empty_context()) } /// Split dictionary into 2 dictionaries at the prefix. pub fn split_by_prefix_ext( &self, key_prefix: &CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(Self, Self), Error> { let (left, right) = ok!(self.dict.split_by_prefix_ext(key_prefix, context)); @@ -592,7 +624,7 @@ where /// /// [`values`]: Dict::values /// [`raw_values`]: Dict::raw_values - pub fn iter<'a>(&'a self) -> AugIter<'_, K, A, V> + pub fn iter<'a>(&'a self) -> AugIter<'a, K, A, V> where V: Load<'a>, { @@ -816,7 +848,7 @@ mod tests { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - _: &mut dyn CellContext, + _: &dyn CellContext, ) -> Result<(), Error> { let left = left.load_bit()?; let right = right.load_bit()?; @@ -832,7 +864,7 @@ mod tests { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - _: &mut dyn CellContext, + _: &dyn CellContext, ) -> Result<(), Error> { let left = left.load_u32()?; let right = right.load_u32()?; @@ -1017,7 +1049,7 @@ mod tests { (4258889371, SomeValue(4956495), 3256452222), ], ] { - let result = AugDict::::try_from_sorted_slice(&entries).unwrap(); + let result = AugDict::::try_from_sorted_slice(entries).unwrap(); let mut dict = AugDict::::new(); for (k, a, v) in entries { @@ -1062,4 +1094,35 @@ mod tests { assert_eq!(built_from_dict, dict); } } + + #[test] + fn search_by_lt() { + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Store, Load)] + struct MaxValue(u64); + + impl AugDictExtra for MaxValue { + fn comp_add( + left: &mut CellSlice, + right: &mut CellSlice, + b: &mut CellBuilder, + _: &dyn CellContext, + ) -> Result<(), Error> { + let left = left.load_u64()?; + let right = right.load_u64()?; + b.store_u64(left.max(right)) + } + } + + let mut items = AugDict::::new(); + items.set(0, MaxValue(100), 123).unwrap(); + items.set(2, MaxValue(150), 234).unwrap(); + items.set(4, MaxValue(200), 345).unwrap(); + items.set(6, MaxValue(350), 456).unwrap(); + items.set(7, MaxValue(300), 567).unwrap(); + items.set(8, MaxValue(250), 678).unwrap(); + + // Search by ordering + let highest = items.find_by_extra(std::cmp::Ordering::Greater).unwrap(); + assert_eq!(highest, Some((6, MaxValue(350), 456))); + } } diff --git a/src/dict/mod.rs b/src/dict/mod.rs index fa77055f..af8ee55a 100644 --- a/src/dict/mod.rs +++ b/src/dict/mod.rs @@ -1,5 +1,7 @@ //! Dictionary implementation. +use std::ops::ControlFlow; + pub use self::aug::*; pub use self::ops::*; pub use self::raw::*; @@ -14,7 +16,9 @@ mod typed; mod ops { pub use self::build::{build_aug_dict_from_sorted_iter, build_dict_from_sorted_iter}; - pub use self::find::{dict_find_bound, dict_find_bound_owned, dict_find_owned}; + pub use self::find::{ + aug_dict_find_by_extra, dict_find_bound, dict_find_bound_owned, dict_find_owned, + }; pub use self::get::{dict_get, dict_get_owned, dict_get_subdict}; pub use self::insert::{aug_dict_insert, dict_insert, dict_insert_owned}; pub use self::remove::{aug_dict_remove_owned, dict_remove_bound_owned, dict_remove_owned}; @@ -71,6 +75,48 @@ impl_dict_key! { }, } +/// `AugDict` search control flow. +pub trait SearchByExtra { + /// Returns if the leaf extra satisfies the condition. + fn on_leaf(&mut self, leaf_extra: &A) -> bool { + _ = leaf_extra; + true + } + + /// Returns which branch satisfies the condition. + fn on_edge(&mut self, left_extra: &A, right_extra: &A) -> ControlFlow<(), Branch>; +} + +impl> SearchByExtra for &mut T { + #[inline] + fn on_leaf(&mut self, leaf_extra: &A) -> bool { + T::on_leaf(self, leaf_extra) + } + + #[inline] + fn on_edge(&mut self, left_extra: &A, right_extra: &A) -> ControlFlow<(), Branch> { + T::on_edge(self, left_extra, right_extra) + } +} + +impl SearchByExtra for Branch { + #[inline] + fn on_edge(&mut self, _: &A, _: &A) -> ControlFlow<(), Branch> { + ControlFlow::Continue(*self) + } +} + +impl SearchByExtra for std::cmp::Ordering { + #[inline] + fn on_edge(&mut self, left_extra: &A, right_extra: &A) -> ControlFlow<(), Branch> { + ControlFlow::Continue(if *self == left_extra.cmp(right_extra) { + Branch::Left + } else { + Branch::Right + }) + } +} + /// Dictionary insertion mode. #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[repr(u8)] @@ -107,14 +153,14 @@ impl SetMode { /// - `builder` - a builder to write the result. /// - `context` - a cell context. pub type AugDictFn = - fn(&mut CellSlice, &mut CellSlice, &mut CellBuilder, &mut dyn CellContext) -> Result<(), Error>; + fn(&mut CellSlice, &mut CellSlice, &mut CellBuilder, &dyn CellContext) -> Result<(), Error>; /// Creates a leaf node fn make_leaf( - key: &CellSlice, + key: &CellSlice<'_>, key_bit_len: u16, value: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { let mut builder = CellBuilder::new(); ok!(write_label(key, key_bit_len, &mut builder)); @@ -124,11 +170,11 @@ fn make_leaf( /// Creates a leaf node with extra value fn make_leaf_with_extra( - key: &CellSlice, + key: &CellSlice<'_>, key_bit_len: u16, extra: &dyn Store, value: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { let mut builder = CellBuilder::new(); ok!(write_label(key, key_bit_len, &mut builder)); @@ -144,7 +190,7 @@ fn split_edge( lcp: &CellSlice, key: &mut CellSlice, value: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { // Advance the key let prev_key_bit_len = key.size_bits(); @@ -181,7 +227,7 @@ fn split_aug_edge( extra: &dyn Store, value: &dyn Store, comparator: AugDictFn, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { // Advance the key let prev_key_bit_len = key.size_bits(); @@ -266,7 +312,7 @@ impl DictBound { pub fn dict_load_from_root( slice: &mut CellSlice<'_>, key_bit_len: u16, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { let mut root = *slice; @@ -288,7 +334,7 @@ pub fn dict_load_from_root( fn rebuild_dict_from_stack( mut segments: Vec>, mut leaf: Cell, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { // Rebuild the tree starting from leaves while let Some(last) = segments.pop() { @@ -318,7 +364,7 @@ fn rebuild_aug_dict_from_stack( mut segments: Vec>, mut leaf: Cell, comparator: AugDictFn, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { // Rebuild the tree starting from leaves while let Some(last) = segments.pop() { @@ -368,7 +414,7 @@ impl Segment<'_> { self, key: &CellSlice<'_>, prev_key_bit_len: u16, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(Cell, Cell), Error> { let index = self.next_branch as u8; @@ -381,11 +427,10 @@ impl Segment<'_> { let value = ok!(context.load_cell(value, LoadMode::Resolve)); // Load parent label - let pfx = { - // SAFETY: `self.data` was already checked for pruned branch access. - let mut parent = unsafe { self.data.as_slice_unchecked() }; - ok!(read_label(&mut parent, prev_key_bit_len)) - }; + let pfx = ok!(read_label( + &mut self.data.as_slice_allow_pruned(), + prev_key_bit_len + )); // Load the opposite branch let mut opposite = match self.data.reference(1 - index) { @@ -464,16 +509,12 @@ fn write_label_parts( let hml_long_len = 2 + bits_for_len + remaining_bits; let hml_same_len = 3 + bits_for_len; - if hml_same_len < hml_long_len && hml_same_len < hml_short_len { - if let Some(pfx_bit) = pfx.test_uniform() { - if pfx_bit == bit { - if let Some(rem_bit) = rem.test_uniform() { - if rem_bit == bit { - return write_hml_same(bit, remaining_bits, bits_for_len, label); - } - } - } - } + if hml_same_len < hml_long_len + && hml_same_len < hml_short_len + && (pfx.is_data_empty() || matches!(pfx.test_uniform(), Some(p) if p == bit)) + && (rem.is_data_empty() || matches!(rem.test_uniform(), Some(r) if r == bit)) + { + return write_hml_same(bit, remaining_bits, bits_for_len, label); } if hml_short_len <= MAX_BIT_LEN && hml_short_len <= hml_long_len { @@ -560,25 +601,26 @@ fn read_hml_same<'a>(label: &mut CellSlice<'a>, bits_for_len: u16) -> Result bool { + /// Converts the branch to a boolean value. + pub fn into_bit(self) -> bool { self == Self::Right } - fn reversed(self) -> Self { + /// Returns the opposite branch. + pub fn reversed(self) -> Self { match self { Self::Left => Self::Right, Self::Right => Self::Left, @@ -660,12 +702,9 @@ mod tests { (4258889371, 3256452222), ], ] { - let result = build_dict_from_sorted_iter( - entries.iter().copied(), - 32, - &mut Cell::empty_context(), - ) - .unwrap(); + let result = + build_dict_from_sorted_iter(entries.iter().copied(), 32, Cell::empty_context()) + .unwrap(); let mut dict = Dict::::new(); for (k, v) in entries { @@ -691,12 +730,9 @@ mod tests { .collect::>(); entries.sort_by_key(|(k, _)| *k); - let built_from_dict = build_dict_from_sorted_iter( - entries.iter().copied(), - 32, - &mut Cell::empty_context(), - ) - .unwrap(); + let built_from_dict = + build_dict_from_sorted_iter(entries.iter().copied(), 32, Cell::empty_context()) + .unwrap(); let mut dict = Dict::::new(); for (k, v) in entries { diff --git a/src/dict/ops/build.rs b/src/dict/ops/build.rs index eec3f506..8b1a8cbd 100644 --- a/src/dict/ops/build.rs +++ b/src/dict/ops/build.rs @@ -9,7 +9,7 @@ use crate::error::Error; pub fn build_dict_from_sorted_iter( entries: I, key_bit_len: u16, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> where I: IntoIterator, @@ -44,7 +44,7 @@ where } } - fn build(self, key_bit_len: u16, context: &mut dyn CellContext) -> Result { + fn build(self, key_bit_len: u16, context: &dyn CellContext) -> Result { match self { Self::Leaf { prefix, value, .. } => { make_leaf(&prefix.as_data_slice(), key_bit_len, &value, context) @@ -58,7 +58,7 @@ where right: Self, key_offset: u16, key_bit_len: u16, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { let left_offset = self.prev_lcp_len(); let split_at = right.prev_lcp_len(); @@ -213,7 +213,7 @@ pub fn build_aug_dict_from_sorted_iter( entries: I, key_bit_len: u16, comparator: AugDictFn, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> where I: IntoIterator, @@ -251,7 +251,7 @@ where } } - fn build(self, key_bit_len: u16, context: &mut dyn CellContext) -> Result { + fn build(self, key_bit_len: u16, context: &dyn CellContext) -> Result { match self { Self::Leaf { prefix, @@ -275,7 +275,7 @@ where key_offset: u16, key_bit_len: u16, comparator: AugDictFn, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { let left_offset = self.prev_lcp_len(); let split_at = right.prev_lcp_len(); diff --git a/src/dict/ops/find.rs b/src/dict/ops/find.rs index be1d3b55..bd828a8b 100644 --- a/src/dict/ops/find.rs +++ b/src/dict/ops/find.rs @@ -1,5 +1,7 @@ +use std::ops::ControlFlow; + use crate::cell::*; -use crate::dict::{read_label, Branch, DictBound, DictOwnedEntry, Segment}; +use crate::dict::{read_label, Branch, DictBound, DictOwnedEntry, SearchByExtra, Segment}; use crate::error::Error; /// Returns cell slice parts of the value corresponding to the key. @@ -10,7 +12,7 @@ pub fn dict_find_owned( towards: DictBound, inclusive: bool, signed: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { if key.size_bits() != key_bit_len { return Err(Error::CellUnderflow); @@ -211,12 +213,12 @@ pub fn dict_find_owned( } /// Finds the specified dict bound and returns a key and a value corresponding to the key. -pub fn dict_find_bound<'a: 'b, 'b>( +pub fn dict_find_bound<'a: 'b, 'b, 'c: 'a>( dict: Option<&'a Cell>, mut key_bit_len: u16, bound: DictBound, signed: bool, - context: &mut dyn CellContext, + context: &'c dyn CellContext, ) -> Result)>, Error> { let mut data = match dict { Some(data) => ok!(context @@ -232,7 +234,6 @@ pub fn dict_find_bound<'a: 'b, 'b>( loop { // Read the key part written in the current edge let prefix = ok!(read_label(&mut data, key_bit_len)); - #[allow(clippy::needless_borrow)] if !prefix.is_data_empty() { ok!(key.store_slice_data(prefix)); } @@ -270,7 +271,7 @@ pub fn dict_find_bound_owned( mut key_bit_len: u16, bound: DictBound, signed: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { let root = match dict { Some(data) => ok!(context.load_cell(data.clone(), LoadMode::Full)), @@ -331,3 +332,132 @@ pub fn dict_find_bound_owned( // Return the last slice as data Ok(Some((key, slice))) } + +/// Searches for an item using a predicate on extra values. +pub fn aug_dict_find_by_extra<'a, A, S>( + dict: Option<&'a Cell>, + mut key_bit_len: u16, + mut flow: S, +) -> Result)>, Error> +where + S: SearchByExtra, + A: Load<'a> + 'a, +{ + struct Leaf<'a, A> { + prefix: CellSlice<'a>, + extra: A, + value: CellSlice<'a>, + } + + struct Edge<'a, A> { + prefix: CellSlice<'a>, + key_bit_len: u16, + extra: A, + left: &'a DynCell, + right: &'a DynCell, + } + + enum Next<'a, A> { + Leaf(Leaf<'a, A>), + Edge(Edge<'a, A>), + } + + impl<'a, A> Next<'a, A> { + fn prefix(&self) -> &CellSlice<'a> { + match self { + Self::Leaf(leaf) => &leaf.prefix, + Self::Edge(edge) => &edge.prefix, + } + } + + fn extra(&'a self) -> &'a A { + match self { + Self::Leaf(leaf) => &leaf.extra, + Self::Edge(edge) => &edge.extra, + } + } + } + + fn preload_branch<'a, A>(data: &'a DynCell, mut key_bit_len: u16) -> Result, Error> + where + A: Load<'a> + 'a, + { + let mut data = ok!(data.as_slice()); + let prefix = ok!(read_label(&mut data, key_bit_len)); + + let is_edge = match key_bit_len.checked_sub(prefix.size_bits()) { + Some(0) => false, + Some(remaining) => { + if data.size_refs() < 2 { + return Err(Error::CellUnderflow); + } + key_bit_len = remaining - 1; + true + } + None => return Err(Error::CellUnderflow), + }; + + let mut children = None; + if is_edge { + let left = ok!(data.load_reference()); + let right = ok!(data.load_reference()); + children = Some((left, right)); + }; + let extra = ok!(A::load_from(&mut data)); + + Ok(match children { + None => Next::Leaf(Leaf { + prefix, + extra, + value: data, + }), + Some((left, right)) => Next::Edge(Edge { + prefix, + key_bit_len, + extra, + left, + right, + }), + }) + } + + let data = match dict { + Some(data) => data.as_ref(), + None => return Ok(None), + }; + + let mut key_builder = CellBuilder::new(); + + let mut next = ok!(preload_branch::(data, key_bit_len)); + loop { + let prefix = next.prefix(); + if !prefix.is_data_empty() { + ok!(key_builder.store_slice_data(prefix)); + } + + match next { + Next::Leaf(leaf) if flow.on_leaf(&leaf.extra) => { + break Ok(Some((key_builder, leaf.extra, leaf.value))); + } + Next::Leaf(_) => break Ok(None), + Next::Edge(edge) => { + key_bit_len = edge.key_bit_len; + + let left = ok!(preload_branch::(edge.left, key_bit_len)); + let right = ok!(preload_branch::(edge.right, key_bit_len)); + + next = match flow.on_edge(left.extra(), right.extra()) { + ControlFlow::Continue(Branch::Left) => { + ok!(key_builder.store_bit_zero()); + left + } + ControlFlow::Continue(Branch::Right) => { + ok!(key_builder.store_bit_one()); + right + } + ControlFlow::Break(()) => return Ok(None), + }; + } + } + } +} diff --git a/src/dict/ops/get.rs b/src/dict/ops/get.rs index 0ee53e37..932f9125 100644 --- a/src/dict/ops/get.rs +++ b/src/dict/ops/get.rs @@ -3,11 +3,11 @@ use crate::dict::{make_leaf, read_label, split_edge, Branch}; use crate::error::Error; /// Returns a `CellSlice` of the value corresponding to the key. -pub fn dict_get<'a: 'b, 'b>( +pub fn dict_get<'a: 'b, 'b, 'c: 'a>( dict: Option<&'a Cell>, key_bit_len: u16, mut key: CellSlice<'b>, - context: &mut dyn CellContext, + context: &'c dyn CellContext, ) -> Result>, Error> { if key.size_bits() != key_bit_len { return Err(Error::CellUnderflow); @@ -60,7 +60,7 @@ pub fn dict_get_owned( dict: Option<&Cell>, key_bit_len: u16, mut key: CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { if key.size_bits() != key_bit_len { return Err(Error::CellUnderflow); @@ -127,11 +127,11 @@ pub fn dict_get_owned( /// Gets subdictionary by specified prefiex /// Returns optional dictionary as Cell representation if specified prefix is present in dictionary -pub fn dict_get_subdict<'a: 'b, 'b>( +pub fn dict_get_subdict<'a: 'b, 'b, 'c: 'a>( dict: Option<&'a Cell>, key_bit_len: u16, prefix: &mut CellSlice<'b>, - context: &mut dyn CellContext, + context: &'c dyn CellContext, ) -> Result, Error> { match dict { None => Ok(None), diff --git a/src/dict/ops/insert.rs b/src/dict/ops/insert.rs index 1d00a650..fa2247cf 100644 --- a/src/dict/ops/insert.rs +++ b/src/dict/ops/insert.rs @@ -13,7 +13,7 @@ pub fn dict_insert( key_bit_len: u16, value: &dyn Store, mode: SetMode, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { if key.size_bits() != key_bit_len { return Err(Error::CellUnderflow); @@ -119,7 +119,7 @@ pub fn aug_dict_insert( value: &dyn Store, mode: SetMode, comparator: AugDictFn, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { if key.size_bits() != key_bit_len { return Err(Error::CellUnderflow); @@ -240,11 +240,11 @@ pub fn dict_insert_owned( key_bit_len: u16, value: &dyn Store, mode: SetMode, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(bool, Option), Error> { fn last( stack: &[Segment], - context: &mut dyn CellContext, + context: &dyn CellContext, mode: LoadMode, ) -> Result, Error> { match stack.last() { diff --git a/src/dict/ops/remove.rs b/src/dict/ops/remove.rs index f083dcaa..d7ff728d 100644 --- a/src/dict/ops/remove.rs +++ b/src/dict/ops/remove.rs @@ -12,7 +12,7 @@ pub fn dict_remove_owned( key: &mut CellSlice, key_bit_len: u16, allow_subtree: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { if !allow_subtree && key.size_bits() != key_bit_len { return Err(Error::CellUnderflow); @@ -50,7 +50,7 @@ pub fn aug_dict_remove_owned( key_bit_len: u16, allow_subtree: bool, comparator: AugDictFn, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { if !allow_subtree && key.size_bits() != key_bit_len { return Err(Error::CellUnderflow); @@ -82,10 +82,10 @@ pub fn aug_dict_remove_owned( Ok(Some((value, removed))) } -fn dict_find_value_to_remove<'a>( +fn dict_find_value_to_remove<'a, 'c: 'a>( root: &'a Cell, key: &mut CellSlice, - context: &mut dyn CellContext, + context: &'c dyn CellContext, ) -> Result>, CellSliceRange, u16)>, Error> { // TODO: change mode to `LoadMode::UseGas` if copy-on-write for libraries is not ok let mut data = ok!(context.load_dyn_cell(root.as_ref(), LoadMode::Full)); @@ -152,7 +152,7 @@ pub fn dict_remove_bound_owned( mut key_bit_len: u16, bound: DictBound, signed: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { let root = match &dict { // TODO: change mode to `LoadMode::UseGas` if copy-on-write for libraries is not ok @@ -219,11 +219,10 @@ pub fn dict_remove_bound_owned( }; // Load parent label - let pfx = { - // SAFETY: `last.data` was already checked for pruned branch access. - let mut parent = unsafe { last.data.as_slice_unchecked() }; - ok!(read_label(&mut parent, prev_key_bit_len)) - }; + let pfx = ok!(read_label( + &mut last.data.as_slice_allow_pruned(), + prev_key_bit_len + )); // Load the opposite branch let mut opposite = match last.data.reference(1 - index) { diff --git a/src/dict/ops/split_merge.rs b/src/dict/ops/split_merge.rs index dbd30be0..55acec13 100644 --- a/src/dict/ops/split_merge.rs +++ b/src/dict/ops/split_merge.rs @@ -7,7 +7,7 @@ pub fn dict_split_by_prefix( dict: Option<&'_ Cell>, key_bit_len: u16, key_prefix: &CellSlice, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(Option, Option), Error> { if key_bit_len == 0 { return Ok((None, None)); @@ -73,7 +73,7 @@ pub fn dict_merge( left: &mut Option, right: &Option, key_bit_length: u16, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match (&left, right) { (None, None) | (Some(_), None) => return Ok(()), diff --git a/src/dict/raw.rs b/src/dict/raw.rs index ffc4709e..699f56f6 100644 --- a/src/dict/raw.rs +++ b/src/dict/raw.rs @@ -63,7 +63,7 @@ impl Store for RawDict { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { self.0.store_into(builder, context) } @@ -126,7 +126,7 @@ impl RawDict { let root = ok!(build_dict_from_sorted_iter( sorted, N, - &mut Cell::empty_context() + Cell::empty_context() )); Ok(Self(root)) } @@ -140,7 +140,7 @@ impl RawDict { let root = ok!(build_dict_from_sorted_iter( sorted.iter().map(|(k, v)| (k, v)), N, - &mut Cell::empty_context() + Cell::empty_context() )); Ok(Self(root)) } @@ -166,7 +166,7 @@ impl RawDict { #[inline] pub fn load_from_root_ext( slice: &mut CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { match dict_load_from_root(slice, N, context) { Ok(root) => Ok(Self(Some(root))), @@ -178,14 +178,14 @@ impl RawDict { /// /// NOTE: Uses the default cell context. pub fn get<'a>(&'a self, key: CellSlice<'_>) -> Result>, Error> { - dict_get(self.0.as_ref(), N, key, &mut Cell::empty_context()) + dict_get(self.0.as_ref(), N, key, Cell::empty_context()) } /// Returns a `CellSlice` of the value corresponding to the key. - pub fn get_ext<'a>( + pub fn get_ext<'a, 'c: 'a>( &'a self, key: CellSlice<'_>, - context: &mut dyn CellContext, + context: &'c dyn CellContext, ) -> Result>, Error> { dict_get(self.0.as_ref(), N, key, context) } @@ -204,15 +204,15 @@ impl RawDict { DictBound::Max, false, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } /// Get subdict of dictionary by specified key prefix - pub fn get_subdict<'a>( + pub fn get_subdict<'a, 'c: 'a>( &'a self, mut prefix: CellSlice<'a>, - context: &mut dyn CellContext, + context: &'c dyn CellContext, ) -> Result, Error> { dict_get_subdict(self.0.as_ref(), N, &mut prefix, context) } @@ -231,7 +231,7 @@ impl RawDict { DictBound::Min, false, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } @@ -249,7 +249,7 @@ impl RawDict { DictBound::Max, true, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } @@ -267,7 +267,7 @@ impl RawDict { DictBound::Min, true, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } @@ -275,14 +275,14 @@ impl RawDict { /// /// NOTE: Uses the default cell context. pub fn get_owned(&self, key: CellSlice<'_>) -> Result, Error> { - dict_get_owned(self.0.as_ref(), N, key, &mut Cell::empty_context()) + dict_get_owned(self.0.as_ref(), N, key, Cell::empty_context()) } /// Returns cell slice parts of the value corresponding to the key. pub fn get_owned_ext( &self, key: CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { dict_get_owned(self.0.as_ref(), N, key, context) } @@ -294,7 +294,7 @@ impl RawDict { N, DictBound::Min, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } @@ -305,7 +305,7 @@ impl RawDict { N, DictBound::Max, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } @@ -315,22 +315,16 @@ impl RawDict { bound: DictBound, signed: bool, ) -> Result)>, Error> { - dict_find_bound( - self.0.as_ref(), - N, - bound, - signed, - &mut Cell::empty_context(), - ) + dict_find_bound(self.0.as_ref(), N, bound, signed, Cell::empty_context()) } /// Finds the specified dict bound and returns a key and a value corresponding to the key. - pub fn get_bound_ext( - &self, + pub fn get_bound_ext<'a, 'c: 'a>( + &'a self, bound: DictBound, signed: bool, - context: &mut dyn CellContext, - ) -> Result)>, Error> { + context: &'c dyn CellContext, + ) -> Result)>, Error> { dict_find_bound(self.0.as_ref(), N, bound, signed, context) } @@ -344,7 +338,7 @@ impl RawDict { N, DictBound::Min, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } @@ -358,7 +352,7 @@ impl RawDict { N, DictBound::Max, signed, - &mut Cell::empty_context(), + Cell::empty_context(), ) } @@ -368,13 +362,7 @@ impl RawDict { bound: DictBound, signed: bool, ) -> Result, Error> { - dict_find_bound_owned( - self.0.as_ref(), - N, - bound, - signed, - &mut Cell::empty_context(), - ) + dict_find_bound_owned(self.0.as_ref(), N, bound, signed, Cell::empty_context()) } /// Finds the specified dict bound and returns a key and cell slice parts corresponding to the key. @@ -382,20 +370,14 @@ impl RawDict { &self, bound: DictBound, signed: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { dict_find_bound_owned(self.0.as_ref(), N, bound, signed, context) } /// Returns `true` if the dictionary contains a value for the specified key. pub fn contains_key(&self, key: CellSlice<'_>) -> Result { - Ok(ok!(dict_get( - self.0.as_ref(), - N, - key, - &mut Cell::empty_context() - )) - .is_some()) + Ok(ok!(dict_get(self.0.as_ref(), N, key, Cell::empty_context())).is_some()) } /// Sets the value associated with the key in the dictionary. @@ -403,7 +385,7 @@ impl RawDict { &mut self, mut key: CellSlice<'_>, value: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { dict_insert(&mut self.0, &mut key, N, &value, SetMode::Set, context) } @@ -414,7 +396,7 @@ impl RawDict { &mut self, mut key: CellSlice<'_>, value: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { dict_insert(&mut self.0, &mut key, N, value, SetMode::Replace, context) } @@ -425,7 +407,7 @@ impl RawDict { &mut self, mut key: CellSlice<'_>, value: &dyn Store, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { dict_insert(&mut self.0, &mut key, N, value, SetMode::Add, context) } @@ -435,7 +417,7 @@ impl RawDict { pub fn remove_ext( &mut self, mut key: CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { dict_remove_owned(&mut self.0, &mut key, N, false, context) } @@ -446,31 +428,31 @@ impl RawDict { &mut self, bound: DictBound, signed: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { dict_remove_bound_owned(&mut self.0, N, bound, signed, context) } /// Split dictionary into 2 dictionaries by the first key bit. pub fn split(&self) -> Result<(Self, Self), Error> { - self.split_by_prefix_ext(&Default::default(), &mut Cell::empty_context()) + self.split_by_prefix_ext(&Default::default(), Cell::empty_context()) } /// Split dictionary into 2 dictionaries by the first key bit. - pub fn split_ext(&self, context: &mut dyn CellContext) -> Result<(Self, Self), Error> { + pub fn split_ext(&self, context: &dyn CellContext) -> Result<(Self, Self), Error> { self.split_by_prefix_ext(&Default::default(), context) } /// Split dictionary into 2 dictionaries at the prefix. pub fn split_by_prefix(&self, key_prefix: &CellSlice<'_>) -> Result<(Self, Self), Error> { - self.split_by_prefix_ext(key_prefix, &mut Cell::empty_context()) + self.split_by_prefix_ext(key_prefix, Cell::empty_context()) } /// Split dictionary into 2 dictionaries at the prefix. pub fn split_by_prefix_ext( &self, key_prefix: &CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(Self, Self), Error> { let (left, right) = ok!(dict_split_by_prefix( self.0.as_ref(), @@ -570,7 +552,7 @@ impl RawDict { /// /// [`set_ext`]: RawDict::set_ext pub fn set(&mut self, key: CellSlice<'_>, value: T) -> Result { - self.set_ext(key, &value, &mut Cell::empty_context()) + self.set_ext(key, &value, Cell::empty_context()) } /// Sets the value associated with the key in the dictionary @@ -580,7 +562,7 @@ impl RawDict { /// /// [`replace_ext`]: RawDict::replace_ext pub fn replace(&mut self, key: CellSlice<'_>, value: T) -> Result { - self.replace_ext(key, &value, &mut Cell::empty_context()) + self.replace_ext(key, &value, Cell::empty_context()) } /// Sets the value associated with key in dictionary, @@ -590,7 +572,7 @@ impl RawDict { /// /// [`add_ext`]: RawDict::add_ext pub fn add(&mut self, key: CellSlice<'_>, value: T) -> Result { - self.add_ext(key, &value, &mut Cell::empty_context()) + self.add_ext(key, &value, Cell::empty_context()) } /// Removes the value associated with key in dictionary. @@ -600,7 +582,7 @@ impl RawDict { /// /// [`remove_ext`]: RawDict::remove_ext pub fn remove(&mut self, key: CellSlice<'_>) -> Result, Error> { - self.remove_ext(key, &mut Cell::empty_context()) + self.remove_ext(key, Cell::empty_context()) } /// Removes the lowest key from the dict. @@ -610,7 +592,7 @@ impl RawDict { /// /// [`remove_bound_ext`]: RawDict::remove_bound_ext pub fn remove_min(&mut self, signed: bool) -> Result, Error> { - self.remove_bound_ext(DictBound::Min, signed, &mut Cell::empty_context()) + self.remove_bound_ext(DictBound::Min, signed, Cell::empty_context()) } /// Removes the largest key from the dict. @@ -620,7 +602,7 @@ impl RawDict { /// /// [`remove_bound_ext`]: RawDict::remove_bound_ext pub fn remove_max(&mut self, signed: bool) -> Result, Error> { - self.remove_bound_ext(DictBound::Max, signed, &mut Cell::empty_context()) + self.remove_bound_ext(DictBound::Max, signed, Cell::empty_context()) } /// Removes the specified dict bound. @@ -634,7 +616,7 @@ impl RawDict { bound: DictBound, signed: bool, ) -> Result, Error> { - self.remove_bound_ext(bound, signed, &mut Cell::empty_context()) + self.remove_bound_ext(bound, signed, Cell::empty_context()) } } @@ -695,7 +677,7 @@ impl<'a> RawOwnedIter<'a> { } } -impl<'a> Iterator for RawOwnedIter<'a> { +impl Iterator for RawOwnedIter<'_> { type Item = Result<(CellBuilder, CellSliceParts), Error>; fn next(&mut self) -> Option { @@ -883,7 +865,7 @@ impl<'a> Iterator for RawIter<'a> { remaining_bit_len, }); // SAFETY: we have just added a new element - break (unsafe { segments.last_mut().unwrap_unchecked() }); + break unsafe { segments.last_mut().unwrap_unchecked() }; } else { // Rewind prefix to_rewind += prefix.size_bits(); @@ -1206,7 +1188,7 @@ impl<'a> RawKeys<'a> { } } -impl<'a> Iterator for RawKeys<'a> { +impl Iterator for RawKeys<'_> { type Item = Result; fn next(&mut self) -> Option { @@ -1274,7 +1256,7 @@ impl<'a> RawOwnedValues<'a> { } } -impl<'a> Iterator for RawOwnedValues<'a> { +impl Iterator for RawOwnedValues<'_> { type Item = Result; fn next(&mut self) -> Option { @@ -1432,7 +1414,7 @@ impl<'a> Iterator for RawValues<'a> { remaining_bit_len, }); // SAFETY: we have just added a new element - break (unsafe { segments.last_mut().unwrap_unchecked() }); + break unsafe { segments.last_mut().unwrap_unchecked() }; } else { segments.pop(); } @@ -1885,8 +1867,8 @@ mod tests { #[derive(Debug, Default)] struct SimpleContext { - used_gas: u64, - loaded_cells: ahash::HashSet, + used_gas: std::cell::Cell, + loaded_cells: std::cell::RefCell>, empty_context: ::EmptyCellContext, } @@ -1895,33 +1877,36 @@ mod tests { const NEW_CELL_GAS: u64 = 100; const OLD_CELL_GAS: u64 = 25; - fn consume_gas(&mut self, cell: &DynCell, mode: LoadMode) { + fn consume_gas(&self, cell: &DynCell, mode: LoadMode) { if mode.use_gas() { - self.used_gas += if self.loaded_cells.insert(*cell.repr_hash()) { + let consumed_gas = if self.loaded_cells.borrow_mut().insert(*cell.repr_hash()) { Self::NEW_CELL_GAS } else { Self::OLD_CELL_GAS }; + + self.used_gas.set(self.used_gas.get() + consumed_gas); } } } impl CellContext for SimpleContext { #[inline] - fn finalize_cell(&mut self, cell: CellParts<'_>) -> Result { - self.used_gas += Self::BUILD_CELL_GAS; + fn finalize_cell(&self, cell: CellParts<'_>) -> Result { + self.used_gas + .set(self.used_gas.get() + Self::BUILD_CELL_GAS); self.empty_context.finalize_cell(cell) } #[inline] - fn load_cell(&mut self, cell: Cell, mode: LoadMode) -> Result { + fn load_cell(&self, cell: Cell, mode: LoadMode) -> Result { self.consume_gas(cell.as_ref(), mode); Ok(cell) } #[inline] - fn load_dyn_cell<'a>( - &mut self, + fn load_dyn_cell<'s: 'a, 'a>( + &self, cell: &'a DynCell, mode: LoadMode, ) -> Result<&'a DynCell, Error> { @@ -1947,26 +1932,26 @@ mod tests { key.store_u32(5)?; dict.get_ext(key.as_data_slice(), context)?.unwrap(); - assert_eq!(context.used_gas, SimpleContext::NEW_CELL_GAS * 5); + assert_eq!(context.used_gas.get(), SimpleContext::NEW_CELL_GAS * 5); - context.used_gas = 0; + context.used_gas.set(0); dict.get_ext(key.as_data_slice(), context)?.unwrap(); - assert_eq!(context.used_gas, SimpleContext::OLD_CELL_GAS * 5); + assert_eq!(context.used_gas.get(), SimpleContext::OLD_CELL_GAS * 5); // Second get - context.used_gas = 0; + context.used_gas.set(0); let mut key = CellBuilder::new(); key.store_u32(9)?; dict.get_ext(key.as_data_slice(), context)?.unwrap(); assert_eq!( - context.used_gas, + context.used_gas.get(), SimpleContext::OLD_CELL_GAS + SimpleContext::NEW_CELL_GAS * 2 ); - context.used_gas = 0; + context.used_gas.set(0); dict.get_ext(key.as_data_slice(), context)?.unwrap(); - assert_eq!(context.used_gas, SimpleContext::OLD_CELL_GAS * 3); + assert_eq!(context.used_gas.get(), SimpleContext::OLD_CELL_GAS * 3); Ok(()) } @@ -1988,26 +1973,26 @@ mod tests { key.store_u32(5)?; dict.get_owned_ext(key.as_data_slice(), context)?.unwrap(); - assert_eq!(context.used_gas, SimpleContext::NEW_CELL_GAS * 5); + assert_eq!(context.used_gas.get(), SimpleContext::NEW_CELL_GAS * 5); - context.used_gas = 0; + context.used_gas.set(0); dict.get_owned_ext(key.as_data_slice(), context)?.unwrap(); - assert_eq!(context.used_gas, SimpleContext::OLD_CELL_GAS * 5); + assert_eq!(context.used_gas.get(), SimpleContext::OLD_CELL_GAS * 5); // Second get - context.used_gas = 0; + context.used_gas.set(0); let mut key = CellBuilder::new(); key.store_u32(9)?; dict.get_owned_ext(key.as_data_slice(), context)?.unwrap(); assert_eq!( - context.used_gas, + context.used_gas.get(), SimpleContext::OLD_CELL_GAS + SimpleContext::NEW_CELL_GAS * 2 ); - context.used_gas = 0; + context.used_gas.set(0); dict.get_owned_ext(key.as_data_slice(), context)?.unwrap(); - assert_eq!(context.used_gas, SimpleContext::OLD_CELL_GAS * 3); + assert_eq!(context.used_gas.get(), SimpleContext::OLD_CELL_GAS * 3); Ok(()) } @@ -2028,7 +2013,7 @@ mod tests { let context = &mut SimpleContext::default(); assert!(dict.remove_ext(key.as_data_slice(), context)?.is_none()); - assert_eq!(context.used_gas, SimpleContext::NEW_CELL_GAS * 2); + assert_eq!(context.used_gas.get(), SimpleContext::NEW_CELL_GAS * 2); // Clear dict let target_gas = [ @@ -2055,7 +2040,7 @@ mod tests { let removed = dict.remove_ext(key.as_data_slice(), context)?; assert!(removed.is_some()); - assert_eq!(context.used_gas, target_gas[i as usize]); + assert_eq!(context.used_gas.get(), target_gas[i as usize]); } Ok(()) @@ -2082,14 +2067,14 @@ mod tests { let (key, _) = dict.get_bound_ext(bound, signed, context)?.unwrap(); let removed = dict.clone().remove_ext(key.as_data_slice(), context)?; assert!(removed.is_some()); - assert_eq!(context.used_gas, target_gas); + assert_eq!(context.used_gas.get(), target_gas); println!("=== {range:?} bound={bound:?} signed={signed} [owned]"); let context = &mut SimpleContext::default(); let (key, _) = dict.get_bound_owned_ext(bound, signed, context)?.unwrap(); let removed = dict.remove_ext(key.as_data_slice(), context)?; assert!(removed.is_some()); - assert_eq!(context.used_gas, target_gas); + assert_eq!(context.used_gas.get(), target_gas); } Ok::<_, anyhow::Error>(()) @@ -2225,7 +2210,7 @@ mod tests { dict.set_ext(key.as_data_slice(), &i, context)?; - assert_eq!(context.used_gas, target_gas[i as usize]); + assert_eq!(context.used_gas.get(), target_gas[i as usize]); println!("==="); } @@ -2246,7 +2231,7 @@ mod tests { SetMode::Set, context, )?; - assert_eq!(context.used_gas, target_gas[i as usize]); + assert_eq!(context.used_gas.get(), target_gas[i as usize]); println!("==="); @@ -2262,7 +2247,7 @@ mod tests { )?; assert_eq!(dict, expected_new_root); - assert_eq!(context.used_gas, target_gas[i as usize]); + assert_eq!(context.used_gas.get(), target_gas[i as usize]); println!("==="); } @@ -2280,7 +2265,7 @@ mod tests { SetMode::Add, context, )?; - assert_eq!(context.used_gas, SimpleContext::NEW_CELL_GAS * 5); // Equivalent to simple get + assert_eq!(context.used_gas.get(), SimpleContext::NEW_CELL_GAS * 5); // Equivalent to simple get println!("==="); @@ -2293,7 +2278,7 @@ mod tests { SetMode::Add, context, )?; - assert_eq!(context.used_gas, SimpleContext::NEW_CELL_GAS * 5); // Equivalent to simple get + assert_eq!(context.used_gas.get(), SimpleContext::NEW_CELL_GAS * 5); // Equivalent to simple get Ok(()) } diff --git a/src/dict/typed.rs b/src/dict/typed.rs index f5b8c781..04cc22ad 100644 --- a/src/dict/typed.rs +++ b/src/dict/typed.rs @@ -8,8 +8,8 @@ use crate::error::Error; use crate::util::*; use super::{ - build_dict_from_sorted_iter, dict_find_bound, dict_find_owned, dict_get, dict_insert, - dict_load_from_root, dict_split_by_prefix, DictBound, DictKey, SetMode, + build_dict_from_sorted_iter, dict_find_bound, dict_find_owned, dict_get, dict_get_owned, + dict_insert, dict_load_from_root, dict_split_by_prefix, DictBound, DictKey, SetMode, }; use super::{dict_remove_bound_owned, raw::*}; @@ -47,7 +47,7 @@ impl Store for Dict { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { self.root.store_into(builder, context) } @@ -158,9 +158,10 @@ impl Dict { impl Dict { /// Loads a non-empty dictionary from a root cell. + #[inline] pub fn load_from_root_ext( slice: &mut CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { match dict_load_from_root(slice, K::BITS, context) { Ok(root) => Ok(Self { @@ -178,6 +179,7 @@ where K: Store + DictKey, { /// Returns `true` if the dictionary contains a value for the specified key. + #[inline] pub fn contains_key(&self, key: Q) -> Result where Q: Borrow, @@ -187,12 +189,12 @@ where K: Store + DictKey, { let mut builder = CellBuilder::new(); - ok!(key.store_into(&mut builder, &mut Cell::empty_context())); + ok!(key.store_into(&mut builder, Cell::empty_context())); Ok(ok!(dict_get( root.as_ref(), K::BITS, builder.as_data_slice(), - &mut Cell::empty_context() + Cell::empty_context() )) .is_some()) } @@ -205,6 +207,7 @@ where K: Store + DictKey, { /// Returns the value corresponding to the key. + #[inline] pub fn get<'a: 'b, 'b, Q>(&'a self, key: Q) -> Result, Error> where Q: Borrow + 'b, @@ -220,12 +223,12 @@ where { let Some(mut value) = ({ let mut builder = CellBuilder::new(); - ok!(key.store_into(&mut builder, &mut Cell::empty_context())); + ok!(key.store_into(&mut builder, Cell::empty_context())); ok!(dict_get( root.as_ref(), K::BITS, builder.as_data_slice(), - &mut Cell::empty_context() + Cell::empty_context() )) }) else { return Ok(None); @@ -241,6 +244,7 @@ where } /// Returns the raw value corresponding to the key. + #[inline] pub fn get_raw<'a: 'b, 'b, Q>(&'a self, key: Q) -> Result>, Error> where Q: Borrow + 'b, @@ -253,12 +257,40 @@ where K: Store + DictKey, { let mut builder = CellBuilder::new(); - ok!(key.store_into(&mut builder, &mut Cell::empty_context())); + ok!(key.store_into(&mut builder, Cell::empty_context())); dict_get( root.as_ref(), K::BITS, builder.as_data_slice(), - &mut Cell::empty_context(), + Cell::empty_context(), + ) + } + + get_raw_impl(&self.root, key.borrow()) + } + + /// Returns cell slice parts of the value corresponding to the key. + /// + /// NOTE: Uses the default cell context. + #[inline] + pub fn get_raw_owned(&self, key: Q) -> Result, Error> + where + Q: Borrow, + { + pub fn get_raw_impl( + root: &Option, + key: &K, + ) -> Result, Error> + where + K: Store + DictKey, + { + let mut builder = CellBuilder::new(); + ok!(key.store_into(&mut builder, Cell::empty_context())); + dict_get_owned( + root.as_ref(), + K::BITS, + builder.as_data_slice(), + Cell::empty_context(), ) } @@ -274,7 +306,7 @@ where Q: Borrow, for<'a> V: Load<'a> + 'static, { - match ok!(self.remove_raw_ext(key, &mut Cell::empty_context())) { + match ok!(self.remove_raw_ext(key, Cell::empty_context())) { Some((cell, range)) => { let mut slice = ok!(range.apply(&cell)); Ok(Some(ok!(V::load_from(&mut slice)))) @@ -291,7 +323,7 @@ where where Q: Borrow, { - self.remove_raw_ext(key, &mut Cell::empty_context()) + self.remove_raw_ext(key, Cell::empty_context()) } /// Removes the lowest key from the dict. @@ -301,7 +333,7 @@ where /// /// [`remove_bound_ext`]: RawDict::remove_bound_ext pub fn remove_min_raw(&mut self, signed: bool) -> Result, Error> { - self.remove_bound_raw_ext(DictBound::Min, signed, &mut Cell::empty_context()) + self.remove_bound_raw_ext(DictBound::Min, signed, Cell::empty_context()) } /// Removes the largest key from the dict. @@ -311,7 +343,7 @@ where /// /// [`remove_bound_ext`]: RawDict::remove_bound_ext pub fn remove_max_raw(&mut self, signed: bool) -> Result, Error> { - self.remove_bound_raw_ext(DictBound::Max, signed, &mut Cell::empty_context()) + self.remove_bound_raw_ext(DictBound::Max, signed, Cell::empty_context()) } /// Removes the specified dict bound. @@ -325,29 +357,29 @@ where bound: DictBound, signed: bool, ) -> Result, Error> { - self.remove_bound_raw_ext(bound, signed, &mut Cell::empty_context()) + self.remove_bound_raw_ext(bound, signed, Cell::empty_context()) } /// Split dictionary into 2 dictionaries by the first key bit. pub fn split(&self) -> Result<(Self, Self), Error> { - self.split_by_prefix_ext(&Default::default(), &mut Cell::empty_context()) + self.split_by_prefix_ext(&Default::default(), Cell::empty_context()) } /// Split dictionary into 2 dictionaries by the first key bit. - pub fn split_ext(&self, context: &mut dyn CellContext) -> Result<(Self, Self), Error> { + pub fn split_ext(&self, context: &dyn CellContext) -> Result<(Self, Self), Error> { self.split_by_prefix_ext(&Default::default(), context) } /// Split dictionary into 2 dictionaries at the prefix. pub fn split_by_prefix(&self, key_prefix: &CellSlice<'_>) -> Result<(Self, Self), Error> { - self.split_by_prefix_ext(key_prefix, &mut Cell::empty_context()) + self.split_by_prefix_ext(key_prefix, Cell::empty_context()) } /// Split dictionary into 2 dictionaries at the prefix. pub fn split_by_prefix_ext( &self, key_prefix: &CellSlice<'_>, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(Self, Self), Error> { let (left, right) = ok!(dict_split_by_prefix( self.root.as_ref(), @@ -374,7 +406,7 @@ where let root = ok!(build_dict_from_sorted_iter( sorted.iter().map(|(k, v)| (k.borrow(), v.borrow())), K::BITS, - &mut Cell::empty_context() + Cell::empty_context() )); Ok(Self { root, @@ -393,7 +425,7 @@ where let root = ok!(build_dict_from_sorted_iter( sorted.iter().map(|(k, v)| (k.borrow(), v.borrow())), K::BITS, - &mut Cell::empty_context() + Cell::empty_context() )); Ok(Self { root, @@ -412,7 +444,7 @@ where Q: Borrow, T: Borrow, { - self.set_ext(key, value, &mut Cell::empty_context()) + self.set_ext(key, value, Cell::empty_context()) } /// Sets the value associated with the key in the dictionary @@ -426,7 +458,7 @@ where Q: Borrow, T: Borrow, { - self.replace_ext(key, value, &mut Cell::empty_context()) + self.replace_ext(key, value, Cell::empty_context()) } /// Sets the value associated with key in dictionary, @@ -440,7 +472,7 @@ where Q: Borrow, T: Borrow, { - self.add_ext(key, value, &mut Cell::empty_context()) + self.add_ext(key, value, Cell::empty_context()) } } @@ -461,7 +493,7 @@ where /// /// [`values`]: Dict::values /// [`raw_values`]: Dict::raw_values - pub fn iter<'a>(&'a self) -> Iter<'_, K, V> + pub fn iter<'a>(&'a self) -> Iter<'a, K, V> where V: Load<'a>, { @@ -478,7 +510,7 @@ where /// /// In the current implementation, iterating over dictionary builds a key /// for each element. - pub fn iter_union<'a>(&'a self, other: &'a Self) -> UnionIter<'_, K, V> + pub fn iter_union<'a>(&'a self, other: &'a Self) -> UnionIter<'a, K, V> where V: Load<'a>, { @@ -568,7 +600,7 @@ where K: DictKey + Store, for<'a> V: Load<'a>, { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let Some((key, (cell, range))) = ({ let mut builder = CellBuilder::new(); ok!(key.store_into(&mut builder, context)); @@ -646,7 +678,7 @@ where K::BITS, bound, signed, - &mut Cell::empty_context() + Cell::empty_context() )) else { return Ok(None); }; @@ -664,7 +696,7 @@ where &mut self, bound: DictBound, signed: bool, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> { let removed = ok!(dict_remove_bound_owned( &mut self.root, @@ -691,10 +723,11 @@ where /// Returns an optional removed value as cell slice parts. /// /// Dict is rebuild using the provided cell context. + #[inline] pub fn remove_raw_ext( &mut self, key: Q, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> where Q: Borrow, @@ -702,13 +735,13 @@ where fn remove_raw_ext_impl( root: &mut Option, key: &K, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result, Error> where K: Store + DictKey, { let mut builder = CellBuilder::new(); - ok!(key.store_into(&mut builder, &mut Cell::empty_context())); + ok!(key.store_into(&mut builder, Cell::empty_context())); dict_remove_owned(root, &mut builder.as_data_slice(), K::BITS, false, context) } @@ -788,7 +821,7 @@ where &mut self, key: Q, value: T, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where Q: Borrow, @@ -803,7 +836,7 @@ where &mut self, key: Q, value: T, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where Q: Borrow, @@ -818,7 +851,7 @@ where &mut self, key: Q, value: T, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where Q: Borrow, @@ -832,14 +865,14 @@ where key: &K, value: &V, mode: SetMode, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result where K: Store + DictKey, V: Store, { let mut key_builder = CellBuilder::new(); - ok!(key.store_into(&mut key_builder, &mut Cell::empty_context())); + ok!(key.store_into(&mut key_builder, Cell::empty_context())); dict_insert( &mut self.root, &mut key_builder.as_data_slice(), @@ -893,7 +926,7 @@ where let map = ok!(ahash::HashMap::::deserialize(deserializer)); // TODO: Build from sorted collection - let cx = &mut Cell::empty_context(); + let cx = Cell::empty_context(); let mut dict = Dict::new(); for (key, value) in map { if let Err(e) = dict.set_ext(key, value, cx) { @@ -1072,7 +1105,7 @@ pub struct Keys<'a, K> { _key: PhantomData, } -impl<'a, K> Clone for Keys<'a, K> { +impl Clone for Keys<'_, K> { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -1108,7 +1141,7 @@ where } } -impl<'a, K> Iterator for Keys<'a, K> +impl Iterator for Keys<'_, K> where K: DictKey, { @@ -1576,6 +1609,22 @@ mod tests { assert_eq!(dict.get_or_next(-1, true).unwrap(), Some((-1, -1))); } + #[test] + fn dict_same_after_remove() -> anyhow::Result<()> { + let mut dict = Dict::::new(); + dict.set(-1, 1)?; + dict.set(-2, 2)?; + + let removed = dict.remove(-2).unwrap(); + assert_eq!(removed, Some(2)); + + let mut dict2 = Dict::::new(); + dict2.set(-1, 1)?; + + assert_eq!(dict, dict2); + Ok(()) + } + #[test] fn get_signed_next() { let cell = Boc::decode_base64("te6ccgEBCwEAaAACAskDAQIBIAQCAgHOCAgCASAEBAIBIAUFAgEgBgYCASAHBwIBIAgIAgEgCQkBAwDgCgBoQgBAJTazb04k/ooV5DE4d+ixdwixajACdzkuZVb6ymgnqyHc1lAAAAAAAAAAAAAAAAAAAA==").unwrap(); diff --git a/src/error.rs b/src/error.rs index 3eeb13e8..a1d98492 100644 --- a/src/error.rs +++ b/src/error.rs @@ -85,6 +85,9 @@ pub enum ParseAddrError { /// Too many address parts. #[error("unexpected address part")] UnexpectedPart, + /// Unexpected or invalid address format. + #[error("invalid address format")] + BadFormat, } /// Error type for block id parsing related errors. diff --git a/src/lib.rs b/src/lib.rs index 2c20b95c..a22d1eae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,18 +13,18 @@ //! ## `Cell` vs `CellSlice` vs `CellBuilder` //! //! - [`Cell`] is an immutable tree and provides only basic methods for accessing -//! nodes and some meta info. +//! nodes and some meta info. //! //! - [`CellSlice`] is a read-only view for a part of some cell. It can only -//! be obtained from an existing cell. A cell contains **up to 1023 bits** and -//! **up to 4 references**. Minimal data unit is bit, so a cell slice is similar -//! to a couple of ranges (bit range and refs range). +//! be obtained from an existing cell. A cell contains **up to 1023 bits** and +//! **up to 4 references**. Minimal data unit is bit, so a cell slice is similar +//! to a couple of ranges (bit range and refs range). //! //! - [`CellBuilder`] is used to create a new cell. It is used as an append-only -//! data structure and is the only way to create a new cell with the provided data. -//! Cell creation depends on a context (e.g. message creation in a wallet or a -//! TVM execution with gas tracking), so [`CellBuilder::build_ext`] accepts -//! a [`CellContext`] parameter which can be used to track and modify cells creation. +//! data structure and is the only way to create a new cell with the provided data. +//! Cell creation depends on a context (e.g. message creation in a wallet or a +//! TVM execution with gas tracking), so [`CellBuilder::build_ext`] accepts +//! a [`CellContext`] parameter which can be used to track and modify cells creation. //! //! ## BOC //! @@ -36,15 +36,15 @@ //! ### Merkle stuff //! //! - Pruned branch is a "building block" of merkle structures. A single pruned branch -//! cell replaces a whole subtree and contains just the hash of its root cell hash. +//! cell replaces a whole subtree and contains just the hash of its root cell hash. //! //! - [`MerkleProof`] contains a subset of original tree of cells. In most cases -//! it is created from [`UsageTree`] of some visited cells. Merkle proof is used -//! to proof that something was presented in the origin tree and provide some additional -//! context. +//! it is created from [`UsageTree`] of some visited cells. Merkle proof is used +//! to proof that something was presented in the origin tree and provide some additional +//! context. //! //! - [`MerkleUpdate`] describes a difference between two trees of cells. It can be -//! applied to old cell to create a new cell. +//! applied to old cell to create a new cell. //! //! ### Numeric stuff //! @@ -69,15 +69,15 @@ //! All models implement [`Load`] and [`Store`] traits for conversion from/to cells. //! //! - [`RawDict`] constrains only key size in bits. It is useful when a dictionary -//! can contain multiple types of values. +//! can contain multiple types of values. //! //! - [`Dict`] is a strongly typed version of definition and is a preferable way -//! of working with this data structure. Key type must implement [`DictKey`] trait, -//! which is implemented for numbers and addresses. +//! of working with this data structure. Key type must implement [`DictKey`] trait, +//! which is implemented for numbers and addresses. //! //! - [`AugDict`] adds additional values for all nodes. You can use it to quickly -//! access a subtotal of values for each subtree. -//! NOTE: this type is partially implemented due to its complexity. +//! access a subtotal of values for each subtree. +//! NOTE: this type is partially implemented due to its complexity. //! //! ## Supported Rust Versions //! diff --git a/src/merkle/proof.rs b/src/merkle/proof.rs index d93a62dc..cea8de5e 100644 --- a/src/merkle/proof.rs +++ b/src/merkle/proof.rs @@ -23,15 +23,13 @@ impl Eq for MerkleProofRef<'_> {} impl PartialEq for MerkleProofRef<'_> { fn eq(&self, other: &Self) -> bool { - self.hash == other.hash && self.depth == other.depth && self.cell == other.cell + self.hash == other.hash && self.depth == other.depth && *self.cell == *other.cell } } impl PartialEq for MerkleProofRef<'_> { fn eq(&self, other: &MerkleProof) -> bool { - self.hash == other.hash - && self.depth == other.depth - && self.cell.as_ref() == other.cell.as_ref() + self.hash == other.hash && self.depth == other.depth && *self.cell == *other.cell } } @@ -99,9 +97,7 @@ impl PartialEq for MerkleProof { impl PartialEq> for MerkleProof { fn eq(&self, other: &MerkleProofRef<'_>) -> bool { - self.hash == other.hash - && self.depth == other.depth - && self.cell.as_ref() == other.cell.as_ref() + self.hash == other.hash && self.depth == other.depth && *self.cell == *other.cell } } @@ -143,7 +139,7 @@ impl Load<'_> for MerkleProof { } impl Store for MerkleProof { - fn store_into(&self, b: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, b: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { if !b.has_capacity(Self::BITS, Self::REFS) { return Err(Error::CellOverflow); } @@ -254,7 +250,7 @@ where } /// Builds a Merkle proof using the specified cell context. - pub fn build_ext(self, context: &mut dyn CellContext) -> Result { + pub fn build_ext(self, context: &dyn CellContext) -> Result { let root = self.root; let cell = ok!(self.build_raw_ext(context)); Ok(MerkleProof { @@ -265,7 +261,7 @@ where } /// Builds a Merkle proof child cell using the specified cell context. - pub fn build_raw_ext(self, context: &mut dyn CellContext) -> Result { + pub fn build_raw_ext(self, context: &dyn CellContext) -> Result { BuilderImpl:: { root: self.root, filter: &self.filter, @@ -278,13 +274,13 @@ where } } -impl<'a, F> MerkleProofBuilder<'a, F> +impl MerkleProofBuilder<'_, F> where F: MerkleFilter, { /// Builds a Merkle proof using an empty cell context. pub fn build(self) -> Result { - self.build_ext(&mut Cell::empty_context()) + self.build_ext(Cell::empty_context()) } } @@ -295,7 +291,7 @@ pub struct MerkleProofExtBuilder<'a, F> { allow_different_root: bool, } -impl<'a, F> MerkleProofExtBuilder<'a, F> { +impl MerkleProofExtBuilder<'_, F> { /// Mark whether the different root is ok for this proof. pub fn allow_different_root(mut self, allow: bool) -> Self { self.allow_different_root = allow; @@ -308,9 +304,9 @@ where F: MerkleFilter, { /// Builds a Merkle proof child cell using the specified cell context. - pub fn build_raw_ext( + pub fn build_raw_ext<'c: 'a>( self, - context: &mut dyn CellContext, + context: &'c dyn CellContext, ) -> Result<(Cell, ahash::HashMap<&'a HashBytes, bool>), Error> { let mut pruned_branches = Default::default(); let mut builder = BuilderImpl { @@ -326,16 +322,16 @@ where } } -struct BuilderImpl<'a, 'b, S = ahash::RandomState> { +struct BuilderImpl<'a, 'b, 'c: 'a, S = ahash::RandomState> { root: &'a DynCell, filter: &'b dyn MerkleFilter, cells: HashMap<&'a HashBytes, Cell, S>, pruned_branches: Option<&'b mut HashMap<&'a HashBytes, bool, S>>, - context: &'b mut dyn CellContext, + context: &'c dyn CellContext, allow_different_root: bool, } -impl<'a, 'b, S> BuilderImpl<'a, 'b, S> +impl BuilderImpl<'_, '_, '_, S> where S: BuildHasher + Default, { @@ -453,7 +449,7 @@ where fn make_pruned_branch_cold( cell: &DynCell, merkle_depth: u8, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { make_pruned_branch(cell, merkle_depth, context) } diff --git a/src/merkle/pruned_branch.rs b/src/merkle/pruned_branch.rs index ae8a9098..91e23796 100644 --- a/src/merkle/pruned_branch.rs +++ b/src/merkle/pruned_branch.rs @@ -5,7 +5,7 @@ use crate::error::Error; pub fn make_pruned_branch( cell: &DynCell, merkle_depth: u8, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result { let descriptor = cell.descriptor(); let cell_level_mask = descriptor.level_mask(); @@ -44,8 +44,7 @@ mod test { builder.build().unwrap() }; - let pruned_branch = - make_pruned_branch(cell.as_ref(), 0, &mut Cell::empty_context()).unwrap(); + let pruned_branch = make_pruned_branch(cell.as_ref(), 0, Cell::empty_context()).unwrap(); assert_eq!(cell.repr_hash(), pruned_branch.hash(0)); assert_eq!(cell.depth(0), pruned_branch.depth(0)); @@ -54,7 +53,7 @@ mod test { assert_eq!(cell.depth(3), virtual_cell.depth(3)); let virtual_pruned_branch = - make_pruned_branch(virtual_cell, 0, &mut Cell::empty_context()).unwrap(); + make_pruned_branch(virtual_cell, 0, Cell::empty_context()).unwrap(); assert_eq!(pruned_branch.as_ref(), virtual_pruned_branch.as_ref()); } } diff --git a/src/merkle/update.rs b/src/merkle/update.rs index 5dcb4e45..588d1b4d 100644 --- a/src/merkle/update.rs +++ b/src/merkle/update.rs @@ -88,7 +88,7 @@ impl Load<'_> for MerkleUpdate { } impl Store for MerkleUpdate { - fn store_into(&self, b: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, b: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { if !b.has_capacity(Self::BITS, Self::REFS) { return Err(Error::CellOverflow); } @@ -121,12 +121,12 @@ impl MerkleUpdate { /// Tries to apply this Merkle update to the specified cell, /// producing a new cell and using an empty cell context. pub fn apply(&self, old: &Cell) -> Result { - self.apply_ext(old, &mut Cell::empty_context()) + self.apply_ext(old, Cell::empty_context()) } /// Tries to apply this Merkle update to the specified cell, /// producing a new cell and using an empty cell context. - pub fn apply_ext(&self, old: &Cell, context: &mut dyn CellContext) -> Result { + pub fn apply_ext(&self, old: &Cell, context: &dyn CellContext) -> Result { if old.as_ref().repr_hash() != &self.old_hash { return Err(Error::InvalidData); } @@ -138,7 +138,7 @@ impl MerkleUpdate { struct Applier<'a> { old_cells: ahash::HashMap, new_cells: ahash::HashMap, - context: &'a mut dyn CellContext, + context: &'a dyn CellContext, } impl Applier<'_> { @@ -456,7 +456,7 @@ where } /// Builds a Merkle update using the specified cell context. - pub fn build_ext(self, context: &mut dyn CellContext) -> Result { + pub fn build_ext(self, context: &dyn CellContext) -> Result { BuilderImpl { old: self.old, new: self.new, @@ -467,24 +467,24 @@ where } } -impl<'a, F> MerkleUpdateBuilder<'a, F> +impl MerkleUpdateBuilder<'_, F> where F: MerkleFilter, { /// Builds a Merkle update using an empty cell context. pub fn build(self) -> Result { - self.build_ext(&mut Cell::empty_context()) + self.build_ext(Cell::empty_context()) } } -struct BuilderImpl<'a, 'b> { +struct BuilderImpl<'a, 'b, 'c: 'a> { old: &'a DynCell, new: &'a DynCell, filter: &'b dyn MerkleFilter, - context: &'b mut dyn CellContext, + context: &'c dyn CellContext, } -impl<'a: 'b, 'b> BuilderImpl<'a, 'b> { +impl<'a: 'b, 'b, 'c: 'a> BuilderImpl<'a, 'b, 'c> { fn build(self) -> Result { struct Resolver<'a, S> { pruned_branches: HashMap<&'a HashBytes, bool, S>, @@ -648,7 +648,7 @@ mod tests { let mut builder = CellBuilder::new(); default - .store_into(&mut builder, &mut Cell::empty_context()) + .store_into(&mut builder, Cell::empty_context()) .unwrap(); let cell = builder.build().unwrap(); @@ -687,7 +687,7 @@ mod tests { // Test serialization let mut builder = CellBuilder::new(); merkle_update - .store_into(&mut builder, &mut Cell::empty_context()) + .store_into(&mut builder, Cell::empty_context()) .unwrap(); builder.build().unwrap(); } diff --git a/src/models/account/mod.rs b/src/models/account/mod.rs index b0937bb3..71429a04 100644 --- a/src/models/account/mod.rs +++ b/src/models/account/mod.rs @@ -35,7 +35,7 @@ impl StorageUsed { /// If the limit is reached, the function will return [`Error::Cancelled`]. pub fn compute(account: &Account, cell_limit: usize) -> Result { let cell = { - let cx = &mut Cell::empty_context(); + let cx = Cell::empty_context(); let mut storage = CellBuilder::new(); storage.store_u64(account.last_trans_lt)?; account.balance.store_into(&mut storage, cx)?; @@ -115,7 +115,7 @@ impl AccountStatus { impl Store for AccountStatus { #[inline] - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_small_uint(*self as u8, 2) } } @@ -221,7 +221,7 @@ impl Store for OptionalAccount { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match &self.0 { None => builder.store_bit_zero(), @@ -307,6 +307,7 @@ pub struct Account { /// State of an existing account. #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "status"))] pub enum AccountState { /// Account exists but has not yet been deployed. Uninit, @@ -331,7 +332,7 @@ impl Store for AccountState { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Uninit => builder.store_small_uint(0b00, 2), @@ -374,10 +375,10 @@ pub struct StateInit { /// Optional special contract flags. pub special: Option, /// Optional contract code. - #[cfg_attr(feature = "serde", serde(with = "crate::boc::OptionBoc"))] + #[cfg_attr(feature = "serde", serde(with = "crate::boc::Boc"))] pub code: Option, /// Optional contract data. - #[cfg_attr(feature = "serde", serde(with = "crate::boc::OptionBoc"))] + #[cfg_attr(feature = "serde", serde(with = "crate::boc::Boc"))] pub data: Option, /// Libraries used in smart-contract. pub libraries: Dict, @@ -405,14 +406,14 @@ impl StateInit { } /// Returns the number of data bits that this struct occupies. - const fn bit_len(&self) -> u16 { + pub const fn bit_len(&self) -> u16 { (1 + self.split_depth.is_some() as u16 * SplitDepth::BITS) + (1 + self.special.is_some() as u16 * SpecialFlags::BITS) + 3 } /// Returns the number of references that this struct occupies. - const fn reference_count(&self) -> u8 { + pub const fn reference_count(&self) -> u8 { self.code.is_some() as u8 + self.data.is_some() as u8 + !self.libraries.is_empty() as u8 } } @@ -440,7 +441,7 @@ impl SpecialFlags { } impl Store for SpecialFlags { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_small_uint(((self.tick as u8) << 1) | self.tock as u8, 2) } } diff --git a/src/models/block/block_extra.rs b/src/models/block/block_extra.rs index de40cf88..bd36a750 100755 --- a/src/models/block/block_extra.rs +++ b/src/models/block/block_extra.rs @@ -83,7 +83,7 @@ impl Store for BlockExtra { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { #[cfg(not(any(feature = "venom", feature = "tycho")))] ok!(builder.store_u32(Self::TAG_V1)); @@ -201,7 +201,7 @@ impl Store for AccountBlock { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let transactions_root = match self.transactions.dict().root() { Some(root) => ok!(root.as_ref().as_slice()), @@ -225,7 +225,7 @@ impl<'a> Load<'a> for AccountBlock { Ok(Self { account: ok!(slice.load_u256()), - transactions: ok!(AugDict::load_from_root(slice, &mut Cell::empty_context())), + transactions: ok!(AugDict::load_from_root(slice, Cell::empty_context())), state_update: ok!(Lazy::load_from(slice)), }) } @@ -298,7 +298,7 @@ impl Store for McBlockExtra { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let tag = if self.copyleft_msgs.is_empty() { Self::TAG_V1 @@ -439,7 +439,7 @@ impl AugDictExtra for ShardFeeCreated { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - cx: &mut dyn CellContext, + cx: &dyn CellContext, ) -> Result<(), Error> { let left = ok!(Self::load_from(left)); let right = ok!(Self::load_from(right)); @@ -523,7 +523,7 @@ impl AsRef<[u8; 64]> for Signature { } impl Store for Signature { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { ok!(builder.store_small_uint(Self::TAG, Self::TAG_LEN)); builder.store_raw(&self.0, 512) } diff --git a/src/models/block/block_id.rs b/src/models/block/block_id.rs index 966ecb3b..e2698e62 100644 --- a/src/models/block/block_id.rs +++ b/src/models/block/block_id.rs @@ -127,6 +127,44 @@ pub struct BlockIdShort { pub seqno: u32, } +impl BlockIdShort { + /// Returns `true` if this block id is for a masterchain block. + /// + /// See [`ShardIdent::MASTERCHAIN`] + #[inline] + pub const fn is_masterchain(&self) -> bool { + self.shard.is_masterchain() + } + + /// Returns a previous block id. + pub fn saturating_prev(&self) -> Self { + self.saturating_sub(1) + } + + /// Returns a next block id. + pub fn saturating_next(&self) -> Self { + self.saturating_add(1) + } + + /// Returns a new block id with the seqno + /// increased at most by the specified value. + pub fn saturating_add(&self, seqno: u32) -> Self { + Self { + shard: self.shard, + seqno: self.seqno.saturating_add(seqno), + } + } + + /// Returns a new block id with the seqno + /// decreased at most by the specified value. + pub fn saturating_sub(&self, seqno: u32) -> Self { + Self { + shard: self.shard, + seqno: self.seqno.saturating_sub(seqno), + } + } +} + impl std::fmt::Display for BlockIdShort { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}:{}", self.shard, self.seqno)) @@ -433,7 +471,7 @@ impl ShardIdent { } impl Store for ShardIdent { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let prefix_len = self.prefix_len() as u8; let prefix_without_tag = self.prefix - self.prefix_tag(); ok!(builder.store_u8(prefix_len)); @@ -508,7 +546,7 @@ impl<'de> serde::Deserialize<'de> for ShardIdent { struct ShardIdentVisitor; - impl<'de> Visitor<'de> for ShardIdentVisitor { + impl Visitor<'_> for ShardIdentVisitor { type Value = ShardIdent; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/src/models/block/block_proof.rs b/src/models/block/block_proof.rs index 8940ce0d..38f4ed38 100644 --- a/src/models/block/block_proof.rs +++ b/src/models/block/block_proof.rs @@ -3,6 +3,8 @@ use crate::dict::Dict; use crate::error::Error; use super::{BlockId, BlockSignature}; +#[cfg(feature = "tycho")] +use crate::models::shard::ConsensusInfo; use crate::models::shard::ValidatorBaseInfo; /// Typed block proof. @@ -24,7 +26,7 @@ impl Store for BlockProof { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let child_cell = match &self.signatures { Some(signatures) => { @@ -73,10 +75,14 @@ impl<'a> Load<'a> for BlockProof { /// Masterchain block signatures. #[derive(Debug, Clone, Store, Load)] -#[tlb(tag = "#11")] +#[cfg_attr(not(feature = "tycho"), tlb(tag = "#11"))] +#[cfg_attr(feature = "tycho", tlb(tag = "#12"))] pub struct BlockSignatures { /// Brief validator basic info. pub validator_info: ValidatorBaseInfo, + /// Mempool bounds info. + #[cfg(feature = "tycho")] + pub consensus_info: ConsensusInfo, /// Total number of signatures. pub signature_count: u32, /// Total validators weight. diff --git a/src/models/block/mod.rs b/src/models/block/mod.rs index 594f693b..26e6bbfd 100755 --- a/src/models/block/mod.rs +++ b/src/models/block/mod.rs @@ -40,12 +40,17 @@ pub struct Block { /// Merkle update for the shard state. pub state_update: Lazy, /// Merkle updates for the outgoing messages queue. + #[cfg(not(feature = "tycho"))] pub out_msg_queue_updates: Option>>, + /// Out messages queue info. + #[cfg(feature = "tycho")] + pub out_msg_queue_updates: OutMsgQueueUpdates, /// Block content. pub extra: Lazy, } impl Block { + #[cfg(not(feature = "tycho"))] const TAG_V1: u32 = 0x11ef55aa; const TAG_V2: u32 = 0x11ef55bb; @@ -86,32 +91,38 @@ impl Store for Block { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { + #[cfg(not(feature = "tycho"))] let tag = if self.out_msg_queue_updates.is_none() { Self::TAG_V1 } else { Self::TAG_V2 }; + #[cfg(feature = "tycho")] + let tag = Self::TAG_V2; ok!(builder.store_u32(tag)); ok!(builder.store_u32(self.global_id as u32)); ok!(builder.store_reference(self.info.cell.clone())); ok!(builder.store_reference(self.value_flow.cell.clone())); - ok!( - if let Some(out_msg_queue_updates) = &self.out_msg_queue_updates { - let cell = { - let mut builder = CellBuilder::new(); - ok!(self.state_update.store_into(&mut builder, context)); - ok!(out_msg_queue_updates.store_into(&mut builder, context)); - ok!(builder.build_ext(context)) - }; - builder.store_reference(cell) - } else { - self.state_update.store_into(builder, context) - } - ); + #[cfg(not(feature = "tycho"))] + let out_msg_queue_updates = self.out_msg_queue_updates.as_ref(); + #[cfg(feature = "tycho")] + let out_msg_queue_updates = Some(&self.out_msg_queue_updates); + + ok!(if let Some(out_msg_queue_updates) = out_msg_queue_updates { + let cell = { + let mut builder = CellBuilder::new(); + ok!(self.state_update.store_into(&mut builder, context)); + ok!(out_msg_queue_updates.store_into(&mut builder, context)); + ok!(builder.build_ext(context)) + }; + builder.store_reference(cell) + } else { + self.state_update.store_into(builder, context) + }); self.extra.store_into(builder, context) } @@ -119,15 +130,23 @@ impl Store for Block { impl<'a> Load<'a> for Block { fn load_from(slice: &mut CellSlice<'a>) -> Result { + #[cfg(not(feature = "tycho"))] let with_out_msg_queue_updates = match ok!(slice.load_u32()) { Self::TAG_V1 => false, Self::TAG_V2 => true, _ => return Err(Error::InvalidTag), }; + #[cfg(feature = "tycho")] + if ok!(slice.load_u32()) != Self::TAG_V2 { + return Err(Error::InvalidTag); + } + let global_id = ok!(slice.load_u32()) as i32; let info = ok!(Lazy::load_from(slice)); let value_flow = ok!(Lazy::load_from(slice)); + + #[cfg(not(feature = "tycho"))] let (state_update, out_msg_queue_updates) = if with_out_msg_queue_updates { let slice = &mut ok!(slice.load_reference_as_slice()); ( @@ -138,6 +157,12 @@ impl<'a> Load<'a> for Block { (ok!(Lazy::load_from(slice)), None) }; + #[cfg(feature = "tycho")] + let (state_update, out_msg_queue_updates) = { + let slice = &mut ok!(slice.load_reference_as_slice()); + (ok!(<_>::load_from(slice)), ok!(<_>::load_from(slice))) + }; + Ok(Self { global_id, info, @@ -287,7 +312,7 @@ impl BlockInfo { /// Set previous block reference (split). pub fn set_prev_ref_after_merge(&mut self, left: &BlockRef, right: &BlockRef) { fn store_split_ref(left: &BlockRef, right: &BlockRef) -> Result { - let cx = &mut Cell::empty_context(); + let cx = Cell::empty_context(); let mut builder = CellBuilder::new(); ok!(builder.store_reference(ok!(CellBuilder::build_from_ext(left, cx)))); ok!(builder.store_reference(ok!(CellBuilder::build_from_ext(right, cx)))); @@ -303,7 +328,7 @@ impl Store for BlockInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let packed_flags = ((self.master_ref.is_some() as u8) << 7) | ((self.after_merge as u8) << 6) @@ -524,7 +549,7 @@ impl PrevBlockRef { } impl Store for PrevBlockRef { - fn store_into(&self, builder: &mut CellBuilder, cx: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, cx: &dyn CellContext) -> Result<(), Error> { match self { PrevBlockRef::Single(block_ref) => block_ref.store_into(builder, cx), PrevBlockRef::AfterMerge { left, right } => { @@ -598,7 +623,7 @@ impl Store for ValueFlow { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let tag = if self.copyleft_rewards.is_empty() { Self::TAG_V1 @@ -669,3 +694,16 @@ impl<'a> Load<'a> for ValueFlow { }) } } + +/// Outgoing message queue updates. +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[tlb(tag = "#1")] +pub struct OutMsgQueueUpdates { + /// Hash of the serialized queue diff. + pub diff_hash: HashBytes, + /// The number of additional queue diffs, excluding the current one, + /// that may still be required by other shards. + pub tail_len: u32, +} diff --git a/src/models/block/shard_hashes.rs b/src/models/block/shard_hashes.rs index d3730db4..54eac916 100755 --- a/src/models/block/shard_hashes.rs +++ b/src/models/block/shard_hashes.rs @@ -139,14 +139,14 @@ impl WorkchainShardHashes { } fn try_build_raw(shards: &[(&ShardIdent, &ShardDescription)]) -> Result { - fn make_leaf(descr: &ShardDescription, cx: &mut dyn CellContext) -> Result { + fn make_leaf(descr: &ShardDescription, cx: &dyn CellContext) -> Result { let mut builder = CellBuilder::new(); ok!(builder.store_bit_zero()); ok!(descr.store_into(&mut builder, cx)); builder.build_ext(cx) } - fn make_edge(left: Cell, right: Cell, cx: &mut dyn CellContext) -> Result { + fn make_edge(left: Cell, right: Cell, cx: &dyn CellContext) -> Result { let mut builder = CellBuilder::new(); ok!(builder.store_bit_one()); ok!(builder.store_reference(left)); @@ -157,7 +157,7 @@ impl WorkchainShardHashes { #[inline] fn read_shard( iter: &mut std::slice::Iter<(&ShardIdent, &ShardDescription)>, - cx: &mut dyn CellContext, + cx: &dyn CellContext, ) -> Result<(ShardIdent, Cell), Error> { match iter.next() { Some((&ident, descr)) => { @@ -169,7 +169,7 @@ impl WorkchainShardHashes { } let shards = &mut shards.iter(); - let cx = &mut Cell::empty_context(); + let cx = Cell::empty_context(); let first = ok!(read_shard(shards, cx)); if first.0.is_full() { @@ -405,7 +405,14 @@ pub struct ShardDescription { /// Catchain seqno in the next block. pub next_catchain_seqno: u32, /// Duplicates the shard ident for the latest block in this shard. + #[cfg(not(feature = "tycho"))] pub next_validator_shard: u64, + /// Top processed to anchor with externals from mempool in the shard. + #[cfg(feature = "tycho")] + pub ext_processed_to_anchor_id: u32, + /// Indicates if any shard block was collated since the previous master. + #[cfg(feature = "tycho")] + pub top_sc_block_updated: bool, /// Minimal referenced seqno of the masterchain block. pub min_ref_mc_seqno: u32, /// Unix timestamp when the latest block in this shard was created. @@ -435,13 +442,23 @@ impl ShardDescription { const TAG_V4: u8 = 0xd; #[cfg(feature = "venom")] const TAG_V5: u8 = 0xe; + + /// Converts a `ShardDescription` to a `BlockId` given a shard identifier. + pub fn as_block_id(&self, shard: ShardIdent) -> BlockId { + BlockId { + shard, + seqno: self.seqno, + root_hash: self.root_hash, + file_hash: self.file_hash, + } + } } impl Store for ShardDescription { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { #[allow(unused_mut)] let mut tag = if self.proof_chain.is_some() { @@ -465,6 +482,8 @@ impl Store for ShardDescription { | ((self.want_split as u8) << 5) | ((self.want_merge as u8) << 4) | ((self.nx_cc_updated as u8) << 3); + #[cfg(feature = "tycho")] + let flags = flags | ((self.top_sc_block_updated as u8) << 2); ok!(builder.store_small_uint(tag, Self::TAG_LEN)); ok!(builder.store_u32(self.seqno)); @@ -475,7 +494,10 @@ impl Store for ShardDescription { ok!(builder.store_u256(&self.file_hash)); ok!(builder.store_u8(flags)); ok!(builder.store_u32(self.next_catchain_seqno)); + #[cfg(not(feature = "tycho"))] ok!(builder.store_u64(self.next_validator_shard)); + #[cfg(feature = "tycho")] + ok!(builder.store_u32(self.ext_processed_to_anchor_id)); ok!(builder.store_u32(self.min_ref_mc_seqno)); ok!(builder.store_u32(self.gen_utime)); ok!(self.split_merge_at.store_into(builder, context)); @@ -540,12 +562,15 @@ impl<'a> Load<'a> for ShardDescription { let file_hash = ok!(slice.load_u256()); let flags = ok!(slice.load_u8()); - if flags & 0b111 != 0 { + if flags & 0b11 != 0 { return Err(Error::InvalidData); } let next_catchain_seqno = ok!(slice.load_u32()); + #[cfg(not(feature = "tycho"))] let next_validator_shard = ok!(slice.load_u64()); + #[cfg(feature = "tycho")] + let ext_processed_to_anchor_id = ok!(slice.load_u32()); let min_ref_mc_seqno = ok!(slice.load_u32()); let gen_utime = ok!(slice.load_u32()); let split_merge_at = ok!(Option::::load_from(slice)); @@ -596,8 +621,13 @@ impl<'a> Load<'a> for ShardDescription { want_split: flags & 0b00100000 != 0, want_merge: flags & 0b00010000 != 0, nx_cc_updated: flags & 0b00001000 != 0, + #[cfg(feature = "tycho")] + top_sc_block_updated: flags & 0b00000100 != 0, next_catchain_seqno, + #[cfg(not(feature = "tycho"))] next_validator_shard, + #[cfg(feature = "tycho")] + ext_processed_to_anchor_id, min_ref_mc_seqno, gen_utime, split_merge_at, @@ -648,7 +678,7 @@ pub enum FutureSplitMerge { } impl Store for FutureSplitMerge { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { match *self { Self::Split { split_utime, @@ -699,7 +729,7 @@ pub struct ProofChain { } impl Store for ProofChain { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { ok!(builder.store_u8(self.len)); builder.store_reference(self.child.clone()) } @@ -1031,7 +1061,7 @@ impl<'a> WorkchainShardsTreeKeysIter<'a> { } } -impl<'a> Iterator for WorkchainShardsTreeKeysIter<'a> { +impl Iterator for WorkchainShardsTreeKeysIter<'_> { type Item = Result; fn next(&mut self) -> Option { @@ -1223,7 +1253,12 @@ mod test { want_merge: false, nx_cc_updated: false, next_catchain_seqno: 0, + #[cfg(not(feature = "tycho"))] next_validator_shard: 0, + #[cfg(feature = "tycho")] + ext_processed_to_anchor_id: 4, + #[cfg(feature = "tycho")] + top_sc_block_updated: true, min_ref_mc_seqno: 0, gen_utime: 0, split_merge_at: None, @@ -1245,6 +1280,10 @@ mod test { let deserialized = BocRepr::decode(serialized).unwrap(); assert_eq!(hashes, deserialized); + for item in deserialized.iter() { + let (_, _) = item.unwrap(); + } + // one missing let input = HashMap::from([ (right, empty_info.clone()), diff --git a/src/models/block/tests/mod.rs b/src/models/block/tests/mod.rs index cb652af8..d796ed8d 100644 --- a/src/models/block/tests/mod.rs +++ b/src/models/block/tests/mod.rs @@ -290,7 +290,7 @@ fn shard_ident_store_load() { fn check_store_load(shard: ShardIdent) { let mut builder = CellBuilder::new(); shard - .store_into(&mut builder, &mut Cell::empty_context()) + .store_into(&mut builder, Cell::empty_context()) .unwrap(); let cell = builder.build().unwrap(); assert_eq!(cell.bit_len(), ShardIdent::BITS); @@ -351,3 +351,118 @@ fn proof_for_shardchain_block() { assert_eq!(serialize_any(proof).as_ref(), boc.as_ref()); } + +#[test] +#[cfg(feature = "tycho")] +fn block_with_tycho_updates_store_load() { + use crate::models::{ExtraCurrencyCollection, GlobalCapabilities}; + + let block = Block { + global_id: 42, + info: Lazy::new(&BlockInfo { + version: 0, + gen_utime_ms: 123, + after_merge: false, + before_split: false, + after_split: false, + want_split: false, + want_merge: true, + key_block: false, + flags: 1, + seqno: 24721433, + vert_seqno: 0, + shard: ShardIdent::new(-1, 0x8000000000000000).unwrap(), + gen_utime: 1674507085, + start_lt: 34671157000000, + end_lt: 34671157000005, + gen_validator_list_hash_short: 3236125243, + gen_catchain_seqno: 343054, + min_ref_mc_seqno: 24721430, + prev_key_block_seqno: 24715347, + gen_software: GlobalVersion { + version: 34, + capabilities: GlobalCapabilities::new(464814), + }, + master_ref: None, + prev_ref: Cell::empty_cell(), + prev_vert_ref: None, + }) + .unwrap(), + value_flow: Lazy::new(&ValueFlow { + from_prev_block: CurrencyCollection { + tokens: Tokens::new(3610625966274374005), + other: ExtraCurrencyCollection::new(), + }, + to_next_block: CurrencyCollection { + tokens: Tokens::new(3610625969470214036), + other: ExtraCurrencyCollection::new(), + }, + imported: CurrencyCollection { + tokens: Tokens::new(0), + other: ExtraCurrencyCollection::new(), + }, + exported: CurrencyCollection { + tokens: Tokens::new(0), + other: ExtraCurrencyCollection::new(), + }, + fees_collected: CurrencyCollection { + tokens: Tokens::new(3195840031), + other: ExtraCurrencyCollection::new(), + }, + fees_imported: CurrencyCollection { + tokens: Tokens::new(1495840031), + other: ExtraCurrencyCollection::new(), + }, + recovered: CurrencyCollection { + tokens: Tokens::new(3195840031), + other: ExtraCurrencyCollection::new(), + }, + created: CurrencyCollection { + tokens: Tokens::new(1700000000), + other: ExtraCurrencyCollection::new(), + }, + minted: CurrencyCollection { + tokens: Tokens::new(0), + other: ExtraCurrencyCollection::new(), + }, + copyleft_rewards: Dict::new(), + }) + .unwrap(), + state_update: Lazy::new(&MerkleUpdate { + old_hash: HashBytes::ZERO, + new_hash: HashBytes::ZERO, + old_depth: 182, + new_depth: 182, + old: Cell::empty_cell(), + new: Cell::empty_cell(), + }) + .unwrap(), + extra: Lazy::new(&BlockExtra { + in_msg_description: Lazy::new(&AugDict::new()).unwrap(), + out_msg_description: Lazy::new(&AugDict::new()).unwrap(), + account_blocks: Lazy::new(&AugDict::new()).unwrap(), + rand_seed: HashBytes::ZERO, + created_by: HashBytes::ZERO, + custom: None, + }) + .unwrap(), + out_msg_queue_updates: OutMsgQueueUpdates { + diff_hash: HashBytes::ZERO, + tail_len: 123, + }, + }; + let encoded = BocRepr::encode(&block).unwrap(); + + let cell = Boc::decode(&*encoded).unwrap(); + + let decoded = cell.parse::().unwrap(); + assert_eq!(decoded, block); + + assert_eq!( + decoded.out_msg_queue_updates, + OutMsgQueueUpdates { + diff_hash: HashBytes::ZERO, + tail_len: 123 + } + ); +} diff --git a/src/models/config/mod.rs b/src/models/config/mod.rs index e8aa6cef..00b3c4dd 100644 --- a/src/models/config/mod.rs +++ b/src/models/config/mod.rs @@ -59,6 +59,13 @@ impl std::ops::DerefMut for BlockchainConfig { pub struct BlockchainConfigParams(Dict); impl BlockchainConfigParams { + /// Creates a dictionary from a raw cell. + /// + /// NOTE: Root is mandatory since the configs dictionary can't be empty. + pub fn from_raw(dict_root: Cell) -> Self { + Self(Dict::from_raw(Some(dict_root))) + } + /// Returns the elector account address (in masterchain). /// /// Uses [`ConfigParam1`]. @@ -278,6 +285,19 @@ impl BlockchainConfigParams { ) } + /// Returns a global id. + pub fn get_global_id(&self) -> Result { + ok!(self.get::()).ok_or(Error::CellUnderflow) + } + + /// Updates a global id. + pub fn set_global_id(&mut self, global_id: i32) -> Result { + self.set_raw( + ConfigParam19::ID, + ok!(CellBuilder::build_from(global_id as u32)), + ) + } + /// Returns gas limits and prices. /// /// Uses [`ConfigParam20`] (for masterchain) or [`ConfigParam21`] (for other workchains). @@ -365,6 +385,7 @@ impl BlockchainConfigParams { /// Returns a catchain config. /// /// Uses [`ConfigParam28`]. + #[cfg(not(feature = "tycho"))] pub fn get_catchain_config(&self) -> Result { ok!(self.get::()).ok_or(Error::CellUnderflow) } @@ -372,10 +393,27 @@ impl BlockchainConfigParams { /// Updates a catchain config. /// /// Uses [`ConfigParam28`]. + #[cfg(not(feature = "tycho"))] pub fn set_catchain_config(&mut self, config: &CatchainConfig) -> Result { self.set_raw(ConfigParam28::ID, ok!(CellBuilder::build_from(config))) } + /// Returns a collation config. + /// + /// Uses [`ConfigParam28`]. + #[cfg(feature = "tycho")] + pub fn get_collation_config(&self) -> Result { + ok!(self.get::()).ok_or(Error::CellUnderflow) + } + + /// Updates a collation config. + /// + /// Uses [`ConfigParam28`]. + #[cfg(feature = "tycho")] + pub fn set_collation_config(&mut self, config: &CollationConfig) -> Result { + self.set_raw(ConfigParam28::ID, ok!(CellBuilder::build_from(config))) + } + /// Returns a consensus config. /// /// Uses [`ConfigParam29`]. @@ -422,6 +460,16 @@ impl BlockchainConfigParams { Ok(ok!(self.contains::()) || ok!(self.contains::())) } + /// Returns the previous validator set. + /// + /// Uses [`ConfigParam33`] (temp prev validators) or [`ConfigParam32`] (prev validators). + pub fn get_previous_validator_set(&self) -> Result, Error> { + match ok!(self.get::()) { + None => self.get::(), + set => Ok(set), + } + } + /// Returns the current validator set. /// /// Uses [`ConfigParam35`] (temp validators) or [`ConfigParam34`] (current validators). @@ -432,6 +480,26 @@ impl BlockchainConfigParams { } } + /// Returns the next validator set. + /// + /// Uses [`ConfigParam37`] (temp next validators) or [`ConfigParam36`] (next validators). + pub fn get_next_validator_set(&self) -> Result, Error> { + match ok!(self.get::()) { + None => self.get::(), + set => Ok(set), + } + } + + /// Returns size limits. + pub fn get_size_limits(&self) -> Result { + ok!(self.get::()).ok_or(Error::CellUnderflow) + } + + /// Updates a global id. + pub fn set_size_limits(&mut self, size_limits: &SizeLimitsConfig) -> Result { + self.set_raw(ConfigParam43::ID, ok!(CellBuilder::build_from(size_limits))) + } + /// Returns `true` if the config contains a param for the specified id. pub fn contains<'a, T: KnownConfigParam<'a>>(&'a self) -> Result { self.0.contains_key(T::ID) @@ -495,7 +563,7 @@ impl BlockchainConfigParams { } impl Store for BlockchainConfigParams { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { match self.0.root() { Some(root) => builder.store_reference(root.clone()), None => Err(Error::InvalidData), @@ -560,7 +628,7 @@ impl<'a, T: Load<'a>> Load<'a> for ParamIdentity { impl Store for ParamIdentity { #[inline] - fn store_into(&self, builder: &mut CellBuilder, cx: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, cx: &dyn CellContext) -> Result<(), Error> { self.0.store_into(builder, cx) } } @@ -605,7 +673,7 @@ where { #[inline] fn load_from(slice: &mut CellSlice<'a>) -> Result { - match Dict::load_from_root_ext(slice, &mut Cell::empty_context()) { + match Dict::load_from_root_ext(slice, Cell::empty_context()) { Ok(value) => Ok(Self(value)), Err(e) => Err(e), } @@ -613,7 +681,7 @@ where } impl Store for NonEmptyDict> { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { match self.0.root() { Some(root) => builder.store_slice(ok!(root.as_slice())), None => Err(Error::InvalidData), @@ -680,17 +748,22 @@ impl<'de> serde::Deserialize<'de> for NonEmptyDict> { macro_rules! define_config_params { ($( $(#[doc = $doc:expr])* + $(#[cfg($($cfg_attrs:tt)*)])? $(#[serde($($serde_attrs:tt)*)])? $id:literal => $ident:ident($($ty:tt)*) ),*$(,)?) => { - $($(#[doc = $doc])* - pub struct $ident; + $( + $(#[cfg($($cfg_attrs)*)])? + $(#[doc = $doc])* + pub struct $ident; - impl<'a> KnownConfigParam<'a> for $ident { - const ID: u32 = $id; + $(#[cfg($($cfg_attrs)*)])? + impl<'a> KnownConfigParam<'a> for $ident { + const ID: u32 = $id; - define_config_params!(@wrapper $($ty)*); - })* + define_config_params!(@wrapper $($ty)*); + } + )* #[cfg(feature = "serde")] impl serde::Serialize for BlockchainConfigParams { @@ -715,15 +788,18 @@ macro_rules! define_config_params { { match key { - $($($id => { - let value = define_config_params!( - @ser - $ident, - value, - $($serde_attrs)* - ); - ok!(map.serialize_entry(&key, &value)); - },)?)* + $( + $(#[cfg($($cfg_attrs)*)])? + $($id => { + let value = define_config_params!( + @ser + $ident, + value, + $($serde_attrs)* + ); + ok!(map.serialize_entry(&key, &value)); + },)? + )* _ => ok!(map.serialize_entry(&key, value.as_ref())), } } @@ -761,12 +837,15 @@ macro_rules! define_config_params { while let Some(key) = access.next_key::()? { let value = match key { - $($($id => define_config_params!( + $( + $(#[cfg($($cfg_attrs)*)])? + $($id => define_config_params!( @de $ident, access, $($serde_attrs)* - ),)?)* + ),)? + )* _ => { let RawValue(cell) = ok!(access.next_value()); cell @@ -902,6 +981,9 @@ define_config_params! { #[serde(transparent)] 18 => ConfigParam18(NonEmptyDict => Dict), + /// Global ID. + 19 => ConfigParam19(i32), + /// Masterchain gas limits and prices. /// /// Contains [`GasLimitsPrices`]. @@ -941,9 +1023,17 @@ define_config_params! { /// Catchain configuration params. /// /// Contains a [`CatchainConfig`]. + #[cfg(not(feature = "tycho"))] #[serde(transparent)] 28 => ConfigParam28(CatchainConfig), + /// Collation configuration params. + /// + /// Contains a [`CollationConfig`]. + #[cfg(feature = "tycho")] + #[serde(transparent)] + 28 => ConfigParam28(CollationConfig), + /// Consensus configuration params. /// /// Contains a [`ConsensusConfig`]. @@ -994,6 +1084,12 @@ define_config_params! { /// Contains a [`ValidatorSet`]. #[serde(transparent)] 37 => ConfigParam37(ValidatorSet), + + /// Size limits. + /// + /// Contains a [`SizeLimitsConfig`]. + #[serde(transparent)] + 43 => ConfigParam43(SizeLimitsConfig), } #[cfg(feature = "serde")] diff --git a/src/models/config/params.rs b/src/models/config/params.rs index 1a2019b4..0a2431d0 100644 --- a/src/models/config/params.rs +++ b/src/models/config/params.rs @@ -1,5 +1,4 @@ -use std::borrow::Cow; -use std::num::{NonZeroU16, NonZeroU32, NonZeroU8}; +use std::num::{NonZeroU16, NonZeroU32}; use everscale_crypto::ed25519; @@ -86,7 +85,7 @@ impl Store for WorkchainDescription { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if !self.is_valid() { return Err(Error::InvalidData); @@ -180,7 +179,7 @@ impl Store for WorkchainFormat { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Basic(value) => { @@ -238,6 +237,25 @@ impl WorkchainFormatExtended { && self.max_addr_len <= Uint12::new(1023) && self.addr_len_step <= Uint12::new(1023) } + + /// Checks that address length is in a valid range and is aligned to the len step. + pub fn check_addr_len(&self, addr_len: u16) -> bool { + let addr_len = Uint12::new(addr_len); + + let is_aligned = || { + if self.addr_len_step.is_zero() { + return false; + } + + let var_part = addr_len - self.min_addr_len; + let step_rem = var_part.into_inner() % self.addr_len_step.into_inner(); + step_rem == 0 + }; + + addr_len >= self.min_addr_len + && addr_len <= self.max_addr_len + && (addr_len == self.min_addr_len || addr_len == self.max_addr_len || is_aligned()) + } } /// Block creation reward. @@ -308,6 +326,26 @@ pub struct StoragePrices { pub mc_cell_price_ps: u64, } +impl StoragePrices { + /// Computes the amount of fees for storing `stats` data for `delta` seconds. + pub fn compute_storage_fee( + &self, + is_masterchain: bool, + delta: u64, + stats: CellTreeStats, + ) -> Tokens { + let mut res = if is_masterchain { + (stats.cell_count as u128 * self.mc_cell_price_ps as u128) + .saturating_add(stats.bit_count as u128 * self.mc_bit_price_ps as u128) + } else { + (stats.cell_count as u128 * self.cell_price_ps as u128) + .saturating_add(stats.bit_count as u128 * self.bit_price_ps as u128) + }; + res = res.saturating_mul(delta as u128); + Tokens::new(shift_ceil_price(res)) + } +} + /// Gas limits and prices. #[derive(Default, Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -334,6 +372,17 @@ pub struct GasLimitsPrices { pub flat_gas_price: u64, } +impl GasLimitsPrices { + /// Converts gas units into tokens. + pub fn compute_gas_fee(&self, gas_used: u64) -> Tokens { + let mut res = self.flat_gas_price as u128; + if let Some(extra_gas) = gas_used.checked_sub(self.flat_gas_limit) { + res = res.saturating_add(shift_ceil_price(self.gas_price as u128 * extra_gas as u128)); + } + Tokens::new(res) + } +} + impl GasLimitsPrices { const TAG_BASE: u8 = 0xdd; const TAG_EXT: u8 = 0xde; @@ -341,7 +390,7 @@ impl GasLimitsPrices { } impl Store for GasLimitsPrices { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { ok!(builder.store_u8(Self::TAG_FLAT_PFX)); ok!(builder.store_u64(self.flat_gas_limit)); ok!(builder.store_u64(self.flat_gas_price)); @@ -443,7 +492,20 @@ pub struct MsgForwardPrices { pub next_frac: u16, } +impl MsgForwardPrices { + /// Computes fees for forwarding the specified amount of data. + pub fn compute_fwd_fee(&self, stats: CellTreeStats) -> Tokens { + let lump = self.lump_price as u128; + let extra = shift_ceil_price( + (stats.cell_count as u128 * self.cell_price as u128) + .saturating_add(stats.bit_count as u128 * self.bit_price as u128), + ); + Tokens::new(lump.saturating_add(extra)) + } +} + /// Catchain configuration params. +#[cfg(not(feature = "tycho"))] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct CatchainConfig { @@ -461,13 +523,15 @@ pub struct CatchainConfig { pub shard_validators_num: u32, } +#[cfg(not(feature = "tycho"))] impl CatchainConfig { const TAG_V1: u8 = 0xc1; const TAG_V2: u8 = 0xc2; } +#[cfg(not(feature = "tycho"))] impl Store for CatchainConfig { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let flags = ((self.isolate_mc_validators as u8) << 1) | (self.shuffle_mc_validators as u8); ok!(builder.store_u8(Self::TAG_V2)); ok!(builder.store_u8(flags)); @@ -478,6 +542,7 @@ impl Store for CatchainConfig { } } +#[cfg(not(feature = "tycho"))] impl<'a> Load<'a> for CatchainConfig { fn load_from(slice: &mut CellSlice<'a>) -> Result { let flags = match slice.load_u8() { @@ -500,7 +565,347 @@ impl<'a> Load<'a> for CatchainConfig { } } +/// Collation configuration params. +/// +/// ```text +/// collation_config_tycho#a6 +/// shuffle_mc_validators:Bool +/// mc_block_min_interval_ms:uint32 +/// max_uncommitted_chain_length:uint8 +/// wu_used_to_import_next_anchor:uint64 +/// msgs_exec_params:MsgsExecutionParams +/// work_units_params:WorkUnitsParams +/// = CollationConfig; +/// +/// collation_config_tycho#a7 +/// shuffle_mc_validators:Bool +/// mc_block_min_interval_ms:uint32 +/// empty_sc_block_interval_ms:uint32 +/// max_uncommitted_chain_length:uint8 +/// wu_used_to_import_next_anchor:uint64 +/// msgs_exec_params:MsgsExecutionParams +/// work_units_params:WorkUnitsParams +/// = CollationConfig; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load, Default)] +#[tlb(tag = "#a6")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CollationConfig { + /// Change the order of validators in the masterchain validators list. + pub shuffle_mc_validators: bool, + + /// Minimum interval between master blocks. + pub mc_block_min_interval_ms: u32, + + /// Time to wait before collating an empty shard block. + pub empty_sc_block_interval_ms: u32, + + /// Maximum length on shard blocks chain after previous master block. + pub max_uncommitted_chain_length: u8, + /// Force import next anchor when wu used exceed limit. + pub wu_used_to_import_next_anchor: u64, + + /// Messages execution params. + pub msgs_exec_params: MsgsExecutionParams, + + /// Params to calculate the collation work in wu. + pub work_units_params: WorkUnitsParams, +} + +/// Messages execution params. +/// +/// ```text +/// msgs_execution_params_tycho#00 +/// buffer_limit:uint32 +/// group_limit:uint16 +/// group_vert_size:uint16 +/// externals_expire_timeout:uint16 +/// open_ranges_limit:uint16 +/// par_0_int_msgs_count_limit:uint32 +/// par_0_ext_msgs_count_limit:uint32 +/// group_slots_fractions:(HashmapE 16 uint8) +/// = MsgsExecutionParams; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load, Default)] +#[tlb(tag = "#00")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MsgsExecutionParams { + /// Maximum limit of messages buffer. + pub buffer_limit: u32, + + /// The horizontal limit of one message group. + /// Shows how many slots can be. + /// One slot may contain messages for some accounts. + /// One account can exist only in one slot. + pub group_limit: u16, + /// The vertical limit of one message group. + /// Shows how many messages can be per one slot in the group. + pub group_vert_size: u16, + + /// The timeout for externals in seconds. + pub externals_expire_timeout: u16, + + /// The maximum number of ranges + /// that we should store in ProcessedUptoInfo maps + pub open_ranges_limit: u16, + + /// The maximum number of incoming internal messages on account. + /// When the internal messages queue on the account exceeds the limit + /// then all messages on this account will be processed in other partition. + pub par_0_int_msgs_count_limit: u32, + + /// The maximum number of incoming externals messages on account. + /// When the external messages queue on the account exceeds the limit + /// then all messages on this account will be processed in other partition. + pub par_0_ext_msgs_count_limit: u32, + + /// The fractions of message group slots + /// for messages subgroups + pub group_slots_fractions: Dict, +} + +/// Params to calculate the collation work in wu. +/// +/// ```text +/// work_units_params_tycho#00 +/// prepare:WorkUnitParamsPrepare +/// execute:WorkUnitParamsExecute +/// finalize:WorkUnitParamsFinalize +/// = WorkUnitsParams; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load, Default)] +#[tlb(tag = "#00")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WorkUnitsParams { + /// Params to calculate messages groups prepare work in wu. + pub prepare: WorkUnitsParamsPrepare, + /// Params to calculate messages execution work in wu. + pub execute: WorkUnitsParamsExecute, + /// Params to calculate block finalization work in wu. + pub finalize: WorkUnitsParamsFinalize, +} + +/// Params to calculate messages groups prepare work in wu. +/// +/// ```text +/// work_units_params_prepare_tycho#00 +/// fixed:uint32 +/// msgs_stats:uint16 +/// remaning_msgs_stats:uint16 +/// read_ext_msgs:uint16 +/// read_int_msgs:uint16 +/// read_new_msgs:uint16 +/// add_to_msg_groups:uint16 +/// = WorkUnitsParamsPrepare; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load, Default)] +#[tlb(tag = "#00")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WorkUnitsParamsPrepare { + /// TODO: Add docs. + pub fixed_part: u32, + /// TODO: Add docs. + pub msgs_stats: u16, + /// TODO: Add docs. + pub remaning_msgs_stats: u16, + /// TODO: Add docs. + pub read_ext_msgs: u16, + /// TODO: Add docs. + pub read_int_msgs: u16, + /// TODO: Add docs. + pub read_new_msgs: u16, + /// TODO: Add docs. + pub add_to_msg_groups: u16, +} + +/// Params to calculate messages execution work in wu. +/// +/// ```text +/// work_units_params_execute_tycho#00 +/// prepare:uint32 +/// execute:uint16 +/// execute_err:uint16 +/// execute_delimiter:uint32 +/// serialize_enqueue:uint16 +/// serialize_dequeue:uint16 +/// insert_new_msgs:uint16 +/// subgroup_size:uint16 +/// = WorkUnitsParamsExecute; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load, Default)] +#[tlb(tag = "#00")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WorkUnitsParamsExecute { + /// TODO: Add docs. + pub prepare: u32, + /// TODO: Add docs. + pub execute: u16, + /// TODO: Add docs. + pub execute_err: u16, + /// TODO: Add docs. + pub execute_delimiter: u32, + /// TODO: Add docs. + pub serialize_enqueue: u16, + /// TODO: Add docs. + pub serialize_dequeue: u16, + /// TODO: Add docs. + pub insert_new_msgs: u16, + /// TODO: Add docs. + pub subgroup_size: u16, +} + +/// Params to calculate block finalization work in wu. +/// +/// ```text +/// work_units_params_finalize_tycho#00 +/// build_transactions:uint16 +/// build_accounts:uint16 +/// build_in_msg:uint16 +/// build_out_msg:uint16 +/// serialize_min:uint32 +/// serialize_accounts:uint16 +/// serialize_msg:uint16 +/// state_update_min:uint32 +/// state_update_accounts:uint16 +/// state_update_msg:uint16 +/// create_diff:uint16 +/// serialize_diff:uint16 +/// apply_diff:uint16 +/// diff_tail_len:uint16 +/// = WorkUnitsParamsFinalize; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load, Default)] +#[tlb(tag = "#00")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WorkUnitsParamsFinalize { + /// TODO: Add docs. + pub build_transactions: u16, + /// TODO: Add docs. + pub build_accounts: u16, + /// TODO: Add docs. + pub build_in_msg: u16, + /// TODO: Add docs. + pub build_out_msg: u16, + /// TODO: Add docs. + pub serialize_min: u32, + /// TODO: Add docs. + pub serialize_accounts: u16, + /// TODO: Add docs. + pub serialize_msg: u16, + /// TODO: Add docs. + pub state_update_min: u32, + /// TODO: Add docs. + pub state_update_accounts: u16, + /// TODO: Add docs. + pub state_update_msg: u16, + /// TODO: Add docs. + pub create_diff: u16, + /// TODO: Add docs. + pub serialize_diff: u16, + /// TODO: Add docs. + pub apply_diff: u16, + /// TODO: Add docs. + pub diff_tail_len: u16, +} + +/// DAG Consensus configuration params +/// +/// ```text +/// consensus_config_tycho#d8 +/// clock_skew_millis:uint16 +/// payload_batch_bytes:uint32 +/// commit_history_rounds:uint8 +/// deduplicate_rounds:uint16 +/// max_consensus_lag_rounds:uint16 +/// payload_buffer_bytes:uint32 +/// broadcast_retry_millis:uint8 +/// download_retry_millis:uint8 +/// download_peers:uint8 +/// download_tasks:uint16 +/// sync_support_rounds:uint16 +/// = ConsensusConfig; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Clone, Eq, PartialEq, Store, Load)] +#[tlb(tag = "#d8")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ConsensusConfig { + /// How far a ready-to-be-signed point (with time in its body) + /// may be in the future compared with signer's local (wall) time. + /// Lower bound is defined by genesis, and then advanced by leaders with every anchor. + /// Anchor time is strictly increasing as it is inherited from anchor candidate in every point. + /// + /// **NOTE: Affects overlay id.** + pub clock_skew_millis: u16, + + /// Hard limit on point payload. Excessive messages will be postponed. + /// + /// **NOTE: Affects overlay id.** + pub payload_batch_bytes: u32, + + /// Limits amount of rounds included in anchor history (points that appears in commit). + /// + /// **NOTE: Affects overlay id.** + pub commit_history_rounds: u16, + + /// Size (amount of rounds) of a sliding window to deduplicate external messages across anchors. + /// + /// **NOTE: Affects overlay id.** + pub deduplicate_rounds: u16, + + /// The max expected distance (amount of rounds) between two sequential top known anchors (TKA), + /// i.e. anchors from two sequential top known blocks (TKB, signed master chain blocks, + /// available to local node, and which state is not necessarily applied by local node). For + /// example, the last TKA=`7` and the config value is `210`, so a newer TKA is expected in + /// range `(8..=217).len() == 210`, i.e. some leader successfully completes its 3 rounds + /// in a row (collects 2F+1 signatures for its anchor trigger), and there are one or + /// two additional mempool rounds for the anchor trigger to be delivered to all nodes, + /// and every collator is expected to create and sign a block containing that new TKA and time. + /// Until a new TKA in range `11..=211'('7+4..<217-3-2`) is received by the local mempool, + /// it will not repeat its per-round routine at round `216` and keeps waiting in a "pause mode". + /// DAG will contain `217` round as it always does for the next round after current. + /// Switch of validator set may be scheduled for `218` round, as its round is not created. + /// + /// Effectively defines feedback from block validation consensus to mempool consensus. + /// + /// **NOTE: Affects overlay id.** + pub max_consensus_lag_rounds: u16, + + /// Hard limit on ring buffer size to cache external messages before they are taken into + /// point payload. Newer messages may push older ones out of the buffer when limit is reached. + pub payload_buffer_bytes: u32, + + /// Every round an instance tries to gather as many points and signatures as it can + /// within some time frame. It is a tradeoff between breaking current round + /// on exactly 2F+1 items (points and/or signatures) and waiting for slow nodes. + pub broadcast_retry_millis: u16, + + /// Every missed dependency (point) is downloaded with a group of simultaneous requests to + /// neighbour peers. Every new group of requests is spawned after previous group completed + /// or this interval elapsed (in order not to wait for some slow responding peer). + pub download_retry_millis: u16, + + /// Amount of peers to request at first download attempt. Amount will increase + /// respectively at each attempt, until 2F peers successfully responded `None` + /// or a verifiable point is found (incorrectly signed points do not count). + pub download_peers: u8, + + /// Limits amount of unique points being simultaneously downloaded (except the first one). + pub download_tasks: u16, + + /// Max duration (amount of rounds) at which local mempool is supposed to keep its history + /// for neighbours to sync. Also limits DAG growth when it syncs, as sync takes time. + pub sync_support_rounds: u16, +} + /// Consensus configuration params. +#[cfg(not(feature = "tycho"))] #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ConsensusConfig { @@ -524,13 +929,15 @@ pub struct ConsensusConfig { pub max_collated_bytes: u32, } +#[cfg(not(feature = "tycho"))] impl ConsensusConfig { const TAG_V1: u8 = 0xd6; const TAG_V2: u8 = 0xd7; } +#[cfg(not(feature = "tycho"))] impl Store for ConsensusConfig { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let flags = self.new_catchain_ids as u8; ok!(builder.store_u8(Self::TAG_V2)); @@ -546,8 +953,11 @@ impl Store for ConsensusConfig { } } +#[cfg(not(feature = "tycho"))] impl<'a> Load<'a> for ConsensusConfig { fn load_from(slice: &mut CellSlice<'a>) -> Result { + use std::num::NonZeroU8; + let (flags, round_candidates) = match slice.load_u8() { Ok(Self::TAG_V1) => (0, ok!(NonZeroU32::load_from(slice))), Ok(Self::TAG_V2) => { @@ -594,107 +1004,123 @@ impl ValidatorSet { const TAG_V1: u8 = 0x11; const TAG_V2: u8 = 0x12; - /// Compoutes a validator subset using a zero seed. + /// Computes a validator subset using a zero seed. + #[cfg(not(feature = "tycho"))] pub fn compute_subset( &self, shard_ident: ShardIdent, cc_config: &CatchainConfig, cc_seqno: u32, ) -> Option<(Vec, u32)> { + if shard_ident.is_masterchain() { + return self.compute_mc_subset(cc_seqno, cc_config.shuffle_mc_validators); + } + let total = self.list.len(); let main = self.main.get() as usize; - let subset = if shard_ident.is_masterchain() { - let count = std::cmp::min(total, main); - if !cc_config.shuffle_mc_validators { - self.list[0..count].to_vec() - } else { - let mut prng = ValidatorSetPRNG::new(shard_ident, cc_seqno); - - let mut indices = vec![0; count]; - for i in 0..count { - let j = prng.next_ranged(i as u64 + 1) as usize; // number 0 .. i - debug_assert!(j <= i); - indices[i] = indices[j]; - indices[j] = i; - } + let mut prng = ValidatorSetPRNG::new(shard_ident, cc_seqno); - let mut subset = Vec::with_capacity(count); - for index in indices.into_iter().take(count) { - subset.push(self.list[index].clone()); - } - subset + let vset = if cc_config.isolate_mc_validators { + if total <= main { + return None; + } + + let mut list = self.list[main..].to_vec(); + + let mut total_weight = 0u64; + for descr in &mut list { + descr.prev_total_weight = total_weight; + total_weight += descr.weight; } + + std::borrow::Cow::Owned(Self { + utime_since: self.utime_since, + utime_until: self.utime_until, + main: self.main, + total_weight, + list, + }) } else { - let mut prng = ValidatorSetPRNG::new(shard_ident, cc_seqno); + std::borrow::Cow::Borrowed(self) + }; - let vset = if cc_config.isolate_mc_validators { - if total <= main { - return None; - } + let count = std::cmp::min(vset.list.len(), cc_config.shard_validators_num as usize); - let mut list = self.list[main..].to_vec(); + let mut nodes = Vec::with_capacity(count); + let mut holes = Vec::<(u64, u64)>::with_capacity(count); + let mut total_wt = vset.total_weight; - let mut total_weight = 0u64; - for descr in &mut list { - descr.prev_total_weight = total_weight; - total_weight += descr.weight; - } + for _ in 0..count { + debug_assert!(total_wt > 0); - Cow::Owned(Self { - utime_since: self.utime_since, - utime_until: self.utime_until, - main: self.main, - total_weight, - list, - }) - } else { - Cow::Borrowed(self) - }; + // Generate a pseudo-random number 0..total_wt-1 + let mut p = prng.next_ranged(total_wt); + + for (prev_total_weight, weight) in &holes { + if p < *prev_total_weight { + break; + } + p += weight; + } - let count = std::cmp::min(vset.list.len(), cc_config.shard_validators_num as usize); + let entry = vset.at_weight(p); - let mut nodes = Vec::with_capacity(count); - let mut holes = Vec::<(u64, u64)>::with_capacity(count); - let mut total_wt = vset.total_weight; + nodes.push(ValidatorDescription { + public_key: entry.public_key, + weight: 1, + adnl_addr: entry.adnl_addr, + mc_seqno_since: 0, + prev_total_weight: 0, + }); + debug_assert!(total_wt >= entry.weight); + total_wt -= entry.weight; - for _ in 0..count { - debug_assert!(total_wt > 0); + let new_hole = (entry.prev_total_weight, entry.weight); + let i = holes.partition_point(|item| item <= &new_hole); + debug_assert!(i == 0 || holes[i - 1] < new_hole); - // Generate a pseudo-random number 0..total_wt-1 - let mut p = prng.next_ranged(total_wt); + holes.insert(i, new_hole); + } - for (prev_total_weight, weight) in &holes { - if p < *prev_total_weight { - break; - } - p += weight; - } + let hash_short = Self::compute_subset_hash_short(&nodes, cc_seqno); - let entry = vset.at_weight(p); + Some((nodes, hash_short)) + } - nodes.push(ValidatorDescription { - public_key: entry.public_key, - weight: 1, - adnl_addr: entry.adnl_addr, - mc_seqno_since: 0, - prev_total_weight: 0, - }); - debug_assert!(total_wt >= entry.weight); - total_wt -= entry.weight; + /// Computes a masterchain validator subset using a zero seed. + /// + /// NOTE: In most cases you should use the more generic [`ValidatorSet::compute_subset`]. + pub fn compute_mc_subset( + &self, + cc_seqno: u32, + shuffle: bool, + ) -> Option<(Vec, u32)> { + let total = self.list.len(); + let main = self.main.get() as usize; - let new_hole = (entry.prev_total_weight, entry.weight); - let i = holes.partition_point(|item| item <= &new_hole); - debug_assert!(i == 0 || holes[i - 1] < new_hole); + let count = std::cmp::min(total, main); + let subset = if !shuffle { + self.list[0..count].to_vec() + } else { + let mut prng = ValidatorSetPRNG::new(ShardIdent::MASTERCHAIN, cc_seqno); - holes.insert(i, new_hole); + let mut indices = vec![0; count]; + for i in 0..count { + let j = prng.next_ranged(i as u64 + 1) as usize; // number 0 .. i + debug_assert!(j <= i); + indices[i] = indices[j]; + indices[j] = i; } - nodes + let mut subset = Vec::with_capacity(count); + for index in indices.into_iter().take(count) { + subset.push(self.list[index].clone()); + } + subset }; - let hash_short = Self::compute_subset_hash_short(subset.as_slice(), cc_seqno); - + let hash_short = Self::compute_subset_hash_short(&subset, cc_seqno); Some((subset, hash_short)) } @@ -721,6 +1147,7 @@ impl ValidatorSet { hash } + #[cfg(not(feature = "tycho"))] fn at_weight(&self, weight_pos: u64) -> &ValidatorDescription { debug_assert!(weight_pos < self.total_weight); debug_assert!(!self.list.is_empty()); @@ -736,7 +1163,7 @@ impl Store for ValidatorSet { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let Ok(total) = u16::try_from(self.list.len()) else { return Err(Error::IntOverflow); @@ -775,7 +1202,7 @@ impl<'a> Load<'a> for ValidatorSet { return Err(Error::InvalidData); } - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let (mut total_weight, validators) = if with_total_weight { let total_weight = ok!(slice.load_u64()); @@ -910,7 +1337,7 @@ impl ValidatorDescription { } impl Store for ValidatorDescription { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let with_mc_seqno = self.mc_seqno_since != 0; let tag = if with_mc_seqno { @@ -1056,6 +1483,48 @@ impl ValidatorSetPRNG { } } +/// size_limits_config_v2#02 +/// max_msg_bits:uint32 +/// max_msg_cells:uint32 +/// max_library_cells:uint32 +/// max_vm_data_depth:uint16 +/// max_ext_msg_size:uint32 +/// max_ext_msg_depth:uint16 +/// max_acc_state_cells:uint32 +/// max_acc_state_bits:uint32 +/// max_acc_public_libraries:uint32 +/// defer_out_queue_size_limit:uint32 = SizeLimitsConfig; +#[derive(Debug, Clone, Eq, PartialEq, Store, Load)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[tlb(tag = "#02")] +pub struct SizeLimitsConfig { + /// Max number of bits in message. + pub max_msg_bits: u32, + /// Max number of cells in message. + pub max_msg_cells: u32, + /// Max number of cells in library. + pub max_library_cells: u32, + /// Max cell tree depth for VM data. + pub max_vm_data_depth: u16, + /// Max number of bytes of a BOC-encoded external message. + pub max_ext_msg_size: u32, + /// Max cell tree depth of an external message. + pub max_ext_msg_depth: u16, + /// Max number of cells per account. + pub max_acc_state_cells: u32, + /// Max number of bits per account. + pub max_acc_state_bits: u32, + /// Max number of public libraries per account. + pub max_acc_public_libraries: u32, + /// Size limit of a deferred out messages queue. + pub defer_out_queue_size_limit: u32, +} + +const fn shift_ceil_price(value: u128) -> u128 { + let r = value & 0xffff != 0; + (value >> 16) + r as u128 +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/models/config/tests/mod.rs b/src/models/config/tests/mod.rs index 2645b795..afa6492c 100644 --- a/src/models/config/tests/mod.rs +++ b/src/models/config/tests/mod.rs @@ -1,12 +1,11 @@ -use std::num::NonZeroU32; - use super::*; -use crate::boc::BocRepr; -use crate::models::{ShardIdent, ShardStateUnsplit}; use crate::prelude::Boc; +#[cfg(not(feature = "tycho"))] #[test] fn simple_config() { + use std::num::NonZeroU32; + let data = Boc::decode(include_bytes!("simple_config.boc")).unwrap(); let blockchain_config = data.parse::().unwrap(); @@ -282,7 +281,11 @@ fn prod_config() { config.get_msg_forward_prices(true).unwrap(); config.get_msg_forward_prices(false).unwrap(); + #[cfg(not(feature = "tycho"))] config.get_catchain_config().unwrap(); + #[cfg(feature = "tycho")] + config.get_collation_config().unwrap(); + config.get_consensus_config().unwrap(); let fundamental_addresses = config.get_fundamental_addresses().unwrap(); @@ -328,8 +331,12 @@ fn create_config() { ); } +#[cfg(not(feature = "tycho"))] #[test] fn validator_subset() { + use crate::boc::BocRepr; + use crate::models::{ShardIdent, ShardStateUnsplit}; + let master_state = BocRepr::decode::(&include_bytes!("test_state_2_master.boc")) .unwrap(); @@ -337,7 +344,9 @@ fn validator_subset() { let mc_state_extra = master_state.load_custom().unwrap().unwrap(); let new_session_seqno = mc_state_extra.validator_info.catchain_seqno; + let cc_config = mc_state_extra.config.get_catchain_config().unwrap(); + let validator_set = mc_state_extra.config.get_current_validator_set().unwrap(); let subset = validator_set diff --git a/src/models/currency.rs b/src/models/currency.rs index d7274ed0..f6feaad3 100644 --- a/src/models/currency.rs +++ b/src/models/currency.rs @@ -8,6 +8,7 @@ use crate::num::{Tokens, VarUint248}; /// Amounts collection. #[derive(Debug, Clone, Eq, PartialEq, Store, Load)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[must_use] pub struct CurrencyCollection { /// Amount in native currency. pub tokens: Tokens, @@ -103,6 +104,14 @@ impl CurrencyCollection { *self = ok!(self.checked_sub(other)); Ok(()) } + + /// Returns the intersection between two currency collections. + pub fn checked_clamp(&self, other: &Self) -> Result { + Ok(Self { + other: ok!(self.other.checked_clamp(&other.other)), + tokens: std::cmp::min(self.tokens, other.tokens), + }) + } } impl From for CurrencyCollection { @@ -127,7 +136,7 @@ impl AugDictExtra for CurrencyCollection { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - cx: &mut dyn CellContext, + cx: &dyn CellContext, ) -> Result<(), Error> { let left = ok!(Self::load_from(left)); let right = ok!(Self::load_from(right)); @@ -138,6 +147,7 @@ impl AugDictExtra for CurrencyCollection { /// Dictionary with amounts for multiple currencies. #[derive(Debug, Clone, Eq, PartialEq, Store, Load)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[must_use] #[repr(transparent)] pub struct ExtraCurrencyCollection(Dict); @@ -207,6 +217,33 @@ impl ExtraCurrencyCollection { } Ok(result) } + + /// Returns the intersection between two extra currency collections. + pub fn checked_clamp(&self, other: &Self) -> Result { + let mut result = self.clone(); + for entry in self.0.iter() { + let (currency_id, balance) = ok!(entry); + match ok!(other.0.get(currency_id)) { + // Other collection has this currency, + // so we must update to the lowest balance. + Some(other_balance) => { + if balance > other_balance { + ok!(result.0.set(currency_id, other_balance)); + } + } + // Other collection doesn't have this currency, + // and we have a non-zero amount. + None if !balance.is_zero() => { + // So we must delete it. + ok!(result.0.remove_raw(currency_id)); + } + // Other collection doesn't have this currency, + // and we have a zero amount. So we can do nothing. + None => {} + } + } + Ok(result) + } } impl From> for ExtraCurrencyCollection { @@ -222,3 +259,22 @@ impl ExactSize for ExtraCurrencyCollection { self.0.exact_size() } } + +#[cfg(test)] +mod tests { + use super::*; + + fn _cc_must_use() -> anyhow::Result<()> { + #[expect(unused_must_use)] + { + CurrencyCollection::new(10).checked_add(&CurrencyCollection::ZERO)?; + } + + #[expect(unused_must_use)] + { + ExtraCurrencyCollection::new().checked_add(&ExtraCurrencyCollection::new())?; + } + + Ok(()) + } +} diff --git a/src/models/message/address.rs b/src/models/message/address.rs index 39fbcfe5..14032fe9 100644 --- a/src/models/message/address.rs +++ b/src/models/message/address.rs @@ -136,7 +136,7 @@ impl Store for IntAddr { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Std(addr) => addr.store_into(builder, context), @@ -238,6 +238,54 @@ impl StdAddr { } } + /// Parses a base64-encoded address. + #[cfg(feature = "base64")] + pub fn from_str_ext( + s: &str, + format: StdAddrFormat, + ) -> Result<(Self, Base64StdAddrFlags), ParseAddrError> { + use base64::prelude::{Engine as _, BASE64_STANDARD, BASE64_URL_SAFE}; + + match s.len() { + 0 => Err(ParseAddrError::Empty), + 66..=69 if format.allow_raw => Self::from_str(s).map(|addr| (addr, Default::default())), + 48 if format.allow_base64 || format.allow_base64_url => { + let mut buffer = [0u8; 36]; + + let base64_url = s.contains(['_', '-']); + + let Ok(36) = if base64_url { + BASE64_URL_SAFE + } else { + BASE64_STANDARD + } + .decode_slice(s, &mut buffer) else { + return Err(ParseAddrError::BadFormat); + }; + + #[cfg(not(fuzzing))] + { + let crc = crc_16(&buffer[..34]); + if buffer[34] as u16 != (crc >> 8) || buffer[35] as u16 != (crc & 0xff) { + return Err(ParseAddrError::BadFormat); + } + } + + let addr = StdAddr::new( + buffer[1] as i8, + HashBytes(buffer[2..34].try_into().unwrap()), + ); + let flags = Base64StdAddrFlags { + testnet: buffer[0] & 0x80 != 0, + base64_url, + bounceable: buffer[0] & 0x40 == 0, + }; + Ok((addr, flags)) + } + _ => Err(ParseAddrError::BadFormat), + } + } + /// Returns `true` if this address is for a masterchain block. /// /// See [`ShardIdent::MASTERCHAIN`] @@ -259,6 +307,32 @@ impl StdAddr { pub const fn prefix(&self) -> u64 { u64::from_be_bytes(*self.address.first_chunk()) } + + /// Returns a pretty-printer for base64-encoded address. + #[cfg(feature = "base64")] + pub const fn display_base64(&self, bounceable: bool) -> DisplayBase64StdAddr<'_> { + DisplayBase64StdAddr { + addr: self, + flags: Base64StdAddrFlags { + testnet: false, + base64_url: false, + bounceable, + }, + } + } + + /// Returns a pretty-printer for URL-safe base64-encoded address. + #[cfg(feature = "base64")] + pub const fn display_base64_url(&self, bounceable: bool) -> DisplayBase64StdAddr<'_> { + DisplayBase64StdAddr { + addr: self, + flags: Base64StdAddrFlags { + testnet: false, + base64_url: true, + bounceable, + }, + } + } } impl Addr for StdAddr { @@ -343,7 +417,7 @@ impl Store for StdAddr { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if !builder.has_capacity(self.bit_len(), 0) { return Err(Error::CellOverflow); @@ -440,7 +514,7 @@ impl<'de> serde::Deserialize<'de> for StdAddr { struct StdAddrVisitor; - impl<'de> Visitor<'de> for StdAddrVisitor { + impl Visitor<'_> for StdAddrVisitor { type Value = StdAddr; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -464,6 +538,132 @@ impl<'de> serde::Deserialize<'de> for StdAddr { } } +/// A helper struct to work with base64-encoded addresses. +#[cfg(feature = "base64")] +pub struct StdAddrBase64Repr; + +#[cfg(all(feature = "base64", feature = "serde"))] +impl StdAddrBase64Repr { + /// Serializes address into a base64-encoded string. + pub fn serialize(addr: &StdAddr, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.collect_str(&DisplayBase64StdAddr { + addr, + flags: Base64StdAddrFlags { + testnet: false, + base64_url: URL_SAFE, + bounceable: false, + }, + }) + } + + /// Deserializes address as a base64-encoded string. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::{Error, Visitor}; + + struct StdAddrBase64Visitor; + + impl Visitor<'_> for StdAddrBase64Visitor { + type Value = StdAddr; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("a standard address") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + StdAddr::from_str_ext(v, StdAddrFormat::any()) + .map(|(addr, _)| addr) + .map_err(E::custom) + } + } + + deserializer.deserialize_str(StdAddrBase64Visitor) + } +} + +/// Parsing options for [`StdAddr::from_str_ext`] +#[cfg(feature = "base64")] +#[derive(Debug, Clone, Copy)] +pub struct StdAddrFormat { + /// Allow raw address (0:000...000). + pub allow_raw: bool, + /// Allow base64-encoded address. + pub allow_base64: bool, + /// Allow URL-safe base64 encoding. + pub allow_base64_url: bool, +} + +#[cfg(feature = "base64")] +impl StdAddrFormat { + /// Allows any address format. + pub const fn any() -> Self { + StdAddrFormat { + allow_raw: true, + allow_base64: true, + allow_base64_url: true, + } + } +} + +/// Base64-encoded address flags. +#[cfg(feature = "base64")] +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub struct Base64StdAddrFlags { + /// Address belongs to testnet. + pub testnet: bool, + /// Use URL-safe base64 encoding. + pub base64_url: bool, + /// Whether to set `bounce` flag during transfer. + pub bounceable: bool, +} + +/// Pretty-printer for [`StdAddr`] in base64 format. +#[cfg(feature = "base64")] +pub struct DisplayBase64StdAddr<'a> { + /// Address to display. + pub addr: &'a StdAddr, + /// Encoding flags. + pub flags: Base64StdAddrFlags, +} + +#[cfg(feature = "base64")] +impl std::fmt::Display for DisplayBase64StdAddr<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use base64::prelude::{Engine as _, BASE64_STANDARD, BASE64_URL_SAFE}; + + let mut buffer = [0u8; 36]; + buffer[0] = (0x51 - (self.flags.bounceable as i32) * 0x40 + + (self.flags.testnet as i32) * 0x80) as u8; + buffer[1] = self.addr.workchain as u8; + buffer[2..34].copy_from_slice(self.addr.address.as_array()); + + let crc = crc_16(&buffer[..34]); + buffer[34] = (crc >> 8) as u8; + buffer[35] = (crc & 0xff) as u8; + + let mut output = [0u8; 48]; + if self.flags.base64_url { + BASE64_URL_SAFE + } else { + BASE64_STANDARD + } + .encode_slice(buffer, &mut output) + .unwrap(); + + // SAFETY: output is guaranteed to contain only ASCII. + let output = unsafe { std::str::from_utf8_unchecked(&output) }; + f.write_str(output) + } +} + /// Variable-length internal address. #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct VarAddr { @@ -525,7 +725,7 @@ impl Store for VarAddr { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if !builder.has_capacity(self.bit_len(), 0) { return Err(Error::CellOverflow); @@ -635,7 +835,7 @@ impl<'de> serde::Deserialize<'de> for ExtAddr { struct ExtAddrVisitor; - impl<'de> Visitor<'de> for ExtAddrVisitor { + impl Visitor<'_> for ExtAddrVisitor { type Value = ExtAddr; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -713,7 +913,7 @@ impl Store for Anycast { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if !builder.has_capacity(self.bit_len(), 0) { return Err(Error::CellOverflow); @@ -812,4 +1012,83 @@ mod tests { }; assert_eq!(var_addr.prefix(), 0xb0bacafeb00b1e5a); } + + #[test] + fn base64_address() { + let addr = "0:84545d4d2cada0ce811705d534c298ca42d29315d03a16eee794cefd191dfa79" + .parse::() + .unwrap(); + assert_eq!( + addr.display_base64(true).to_string(), + "EQCEVF1NLK2gzoEXBdU0wpjKQtKTFdA6Fu7nlM79GR36eWpw" + ); + assert_eq!( + StdAddr::from_str_ext( + "EQCEVF1NLK2gzoEXBdU0wpjKQtKTFdA6Fu7nlM79GR36eWpw", + StdAddrFormat::any() + ) + .unwrap(), + ( + addr, + Base64StdAddrFlags { + testnet: false, + base64_url: false, + bounceable: true, + } + ) + ); + + let addr = "0:dddde93b1d3398f0b4305c08de9a032e0bc1b257c4ce2c72090aea1ff3e9ecfd" + .parse::() + .unwrap(); + assert_eq!( + addr.display_base64_url(false).to_string(), + "UQDd3ek7HTOY8LQwXAjemgMuC8GyV8TOLHIJCuof8-ns_Tyv" + ); + assert_eq!( + addr.display_base64(false).to_string(), + "UQDd3ek7HTOY8LQwXAjemgMuC8GyV8TOLHIJCuof8+ns/Tyv" + ); + + assert_eq!( + StdAddr::from_str_ext( + "UQDd3ek7HTOY8LQwXAjemgMuC8GyV8TOLHIJCuof8+ns/Tyv", + StdAddrFormat::any() + ) + .unwrap(), + ( + addr.clone(), + Base64StdAddrFlags { + testnet: false, + base64_url: false, + bounceable: false, + } + ) + ); + + assert_eq!( + addr.display_base64_url(true).to_string(), + "EQDd3ek7HTOY8LQwXAjemgMuC8GyV8TOLHIJCuof8-ns_WFq" + ); + assert_eq!( + addr.display_base64(true).to_string(), + "EQDd3ek7HTOY8LQwXAjemgMuC8GyV8TOLHIJCuof8+ns/WFq" + ); + + assert_eq!( + StdAddr::from_str_ext( + "EQDd3ek7HTOY8LQwXAjemgMuC8GyV8TOLHIJCuof8-ns_WFq", + StdAddrFormat::any() + ) + .unwrap(), + ( + addr, + Base64StdAddrFlags { + testnet: false, + base64_url: true, + bounceable: true, + } + ) + ); + } } diff --git a/src/models/message/envelope.rs b/src/models/message/envelope.rs index 08ee24da..e246f6bd 100644 --- a/src/models/message/envelope.rs +++ b/src/models/message/envelope.rs @@ -78,7 +78,7 @@ impl From for IntermediateAddr { } impl Store for IntermediateAddr { - fn store_into(&self, builder: &mut CellBuilder, cx: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, cx: &dyn CellContext) -> Result<(), Error> { match self { IntermediateAddr::Regular(addr) => { ok!(builder.store_bit_zero()); // tag = $0 diff --git a/src/models/message/in_message.rs b/src/models/message/in_message.rs index 2096ecee..ffdcbcd3 100644 --- a/src/models/message/in_message.rs +++ b/src/models/message/in_message.rs @@ -22,7 +22,7 @@ impl AugDictExtra for ImportFees { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - cx: &mut dyn CellContext, + cx: &dyn CellContext, ) -> Result<(), Error> { let left = ok!(Self::load_from(left)); let right = ok!(Self::load_from(right)); @@ -209,7 +209,7 @@ impl InMsg { } impl Store for InMsg { - fn store_into(&self, builder: &mut CellBuilder, cx: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, cx: &dyn CellContext) -> Result<(), Error> { match self { Self::External(msg) => { ok!(builder.store_small_uint(Self::MSG_IMPORT_EXT, 3)); diff --git a/src/models/message/mod.rs b/src/models/message/mod.rs index 3fcb57a4..b08a7513 100644 --- a/src/models/message/mod.rs +++ b/src/models/message/mod.rs @@ -1,5 +1,7 @@ //! Message models. +use std::borrow::Borrow; + use crate::cell::*; use crate::error::Error; use crate::num::*; @@ -76,7 +78,7 @@ where let mut builder = CellBuilder::new(); ok!(self .0 - .store_body(false, &mut builder, &mut Cell::empty_context()) + .store_body(false, &mut builder, Cell::empty_context()) .map_err(Error::custom)); let cell = ok!(builder.build().map_err(Error::custom)); crate::boc::Boc::serialize(&cell, serializer) @@ -131,6 +133,13 @@ where } } +impl, B> BaseMessage { + /// Returns the type of this message. + pub fn ty(&self) -> MsgType { + self.info.borrow().ty() + } +} + impl BaseMessage { /// Computes the most optimal layout of the message parts. pub fn compute_layout(info: &I, init: Option<&StateInit>, body: &B) -> MessageLayout { @@ -147,7 +156,7 @@ where fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let info_size = self.info.exact_size(); let body_size = self.body.exact_size(); @@ -223,16 +232,16 @@ trait StoreBody { &self, to_cell: bool, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error>; } -impl<'a> StoreBody for CellSlice<'a> { +impl StoreBody for CellSlice<'_> { fn store_body( &self, to_cell: bool, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { SliceOrCell { to_cell, @@ -247,7 +256,7 @@ impl StoreBody for CellSliceParts { &self, to_cell: bool, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let (cell, range) = self; if to_cell && range.is_full(cell.as_ref()) { @@ -301,7 +310,7 @@ impl SliceOrCell { fn store_only_value_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if self.to_cell { let cell = { @@ -320,7 +329,7 @@ impl Store for SliceOrCell { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ok!(builder.store_bit(self.to_cell)); self.store_only_value_into(builder, context) @@ -543,7 +552,7 @@ impl Store for RelaxedMsgInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Int(info) => { @@ -576,6 +585,56 @@ impl<'a> Load<'a> for RelaxedMsgInfo { } } +/// Message type. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum MsgType { + /// Internal message. + Int, + /// External incoming message. + ExtIn, + /// External outgoing message. + ExtOut, +} + +impl MsgType { + /// Returns whether this message is internal. + pub const fn is_internal(&self) -> bool { + matches!(self, Self::Int) + } + + /// Returns whether this message is external incoming. + pub const fn is_external_in(&self) -> bool { + matches!(self, Self::ExtIn) + } + + /// Returns whether this message is external outgoing. + pub const fn is_external_out(&self) -> bool { + matches!(self, Self::ExtOut) + } +} + +impl Store for MsgType { + fn store_into(&self, b: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { + match self { + Self::Int => b.store_bit_zero(), + Self::ExtIn => b.store_small_uint(0b10, 2), + Self::ExtOut => b.store_small_uint(0b11, 2), + } + } +} + +impl<'a> Load<'a> for MsgType { + fn load_from(slice: &mut CellSlice<'a>) -> Result { + Ok(if !ok!(slice.load_bit()) { + Self::Int + } else if !ok!(slice.load_bit()) { + Self::ExtIn + } else { + Self::ExtOut + }) + } +} + /// Message info. #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -590,6 +649,30 @@ pub enum MsgInfo { } impl MsgInfo { + /// Returns the type of this message info. + pub const fn ty(&self) -> MsgType { + match self { + Self::Int(_) => MsgType::Int, + Self::ExtIn(_) => MsgType::ExtIn, + Self::ExtOut(_) => MsgType::ExtOut, + } + } + + /// Returns whether this message is internal. + pub const fn is_internal(&self) -> bool { + matches!(self, Self::Int(_)) + } + + /// Returns whether this message is external incoming. + pub const fn is_external_in(&self) -> bool { + matches!(self, Self::ExtIn(_)) + } + + /// Returns whether this message is external outgoing. + pub const fn is_external_out(&self) -> bool { + matches!(self, Self::ExtOut(_)) + } + /// Exact size of this value when it is stored in slice. pub const fn exact_size_const(&self) -> Size { Size { @@ -626,7 +709,7 @@ impl Store for MsgInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Int(info) => { @@ -728,7 +811,7 @@ impl Store for IntMsgInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let flags = ((self.ihr_disabled as u8) << 2) | ((self.bounce as u8) << 1) | self.bounced as u8; @@ -823,7 +906,7 @@ impl Store for RelaxedIntMsgInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let flags = ((self.ihr_disabled as u8) << 2) | ((self.bounce as u8) << 1) | self.bounced as u8; @@ -887,7 +970,7 @@ impl Store for ExtInMsgInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if !self.import_fee.is_valid() { return Err(Error::InvalidData); @@ -940,7 +1023,7 @@ impl Store for ExtOutMsgInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if !builder.has_capacity(self.bit_len(), 0) { return Err(Error::CellOverflow); @@ -992,7 +1075,7 @@ impl Store for RelaxedExtOutMsgInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { ok!(store_opt_int_addr(builder, context, &self.src)); ok!(store_ext_addr(builder, context, &self.dst)); @@ -1021,7 +1104,7 @@ const fn compute_ext_addr_bit_len(addr: &Option) -> u16 { fn store_ext_addr( builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, addr: &Option, ) -> Result<(), Error> { match addr { @@ -1066,7 +1149,7 @@ const fn compute_opt_int_addr_bit_len(addr: &Option) -> u16 { fn store_opt_int_addr( builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, addr: &Option, ) -> Result<(), Error> { match addr { diff --git a/src/models/message/out_message.rs b/src/models/message/out_message.rs index f315a086..af2f5926 100644 --- a/src/models/message/out_message.rs +++ b/src/models/message/out_message.rs @@ -173,7 +173,7 @@ impl OutMsg { } impl Store for OutMsg { - fn store_into(&self, builder: &mut CellBuilder, cx: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, cx: &dyn CellContext) -> Result<(), Error> { match self { OutMsg::External(msg) => { ok!(builder.store_small_uint(Self::OUT_MSG_EXT, 3)); diff --git a/src/models/message/tests/mod.rs b/src/models/message/tests/mod.rs index a1795a45..575039d1 100644 --- a/src/models/message/tests/mod.rs +++ b/src/models/message/tests/mod.rs @@ -37,6 +37,8 @@ fn check_message(boc: &[u8]) -> Cell { #[test] fn external_message() -> anyhow::Result<()> { let boc = check_message(include_bytes!("external_message.boc")); + assert_eq!(boc.parse::()?, MsgType::ExtIn); + let body = Boc::decode(include_bytes!("external_message_body.boc")).unwrap(); let serialized = serialize_message(Message { info: MsgInfo::ExtIn(ExtInMsgInfo { @@ -57,6 +59,8 @@ fn external_message() -> anyhow::Result<()> { #[test] fn external_outgoing() { let boc = check_message(include_bytes!("external_out_message.boc")); + assert_eq!(boc.parse::().unwrap(), MsgType::ExtOut); + let body = Boc::decode_base64("te6ccgEBAQEADgAAGJMdgs1k/wsgCERmwQ==").unwrap(); let serialized = serialize_message(Message { info: MsgInfo::ExtOut(ExtOutMsgInfo { @@ -77,6 +81,7 @@ fn external_outgoing() { #[test] fn internal_message_empty() { let boc = check_message(include_bytes!("empty_internal_message.boc")); + assert_eq!(boc.parse::().unwrap(), MsgType::Int); let serialized = serialize_message(Message { info: MsgInfo::Int(IntMsgInfo { @@ -103,6 +108,8 @@ fn internal_message_empty() { #[test] fn internal_message_with_body() -> anyhow::Result<()> { let boc = check_message(include_bytes!("internal_message_with_body.boc")); + assert_eq!(boc.parse::().unwrap(), MsgType::Int); + let body = Boc::decode(include_bytes!("internal_message_body.boc")).unwrap(); let serialized = serialize_message(Message { @@ -132,6 +139,7 @@ fn internal_message_with_body() -> anyhow::Result<()> { #[test] fn internal_message_with_deploy() -> anyhow::Result<()> { let boc = check_message(include_bytes!("internal_message_with_deploy.boc")); + assert_eq!(boc.parse::().unwrap(), MsgType::Int); let init = Boc::decode(include_bytes!( "internal_message_with_deploy_state_init.boc" @@ -170,6 +178,7 @@ fn internal_message_with_deploy_special() -> anyhow::Result<()> { use crate::models::account::*; let boc = check_message(include_bytes!("internal_message_with_deploy_special.boc")); + assert_eq!(boc.parse::().unwrap(), MsgType::Int); let init = StateInit { split_depth: None, diff --git a/src/models/mod.rs b/src/models/mod.rs index 56ef0dae..6673f9e2 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -158,7 +158,7 @@ impl<'a, T: Load<'a> + 'a> Lazy { } impl Store for Lazy { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_reference(self.cell.clone()) } } diff --git a/src/models/shard/mod.rs b/src/models/shard/mod.rs index 72424039..510fd278 100755 --- a/src/models/shard/mod.rs +++ b/src/models/shard/mod.rs @@ -17,6 +17,8 @@ use crate::models::ShardIdentFull; pub use self::shard_accounts::*; pub use self::shard_extra::*; +#[cfg(feature = "tycho")] +use super::MsgsExecutionParams; #[cfg(feature = "venom")] use super::ShardBlockRefs; @@ -40,7 +42,7 @@ impl Store for ShardState { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Unsplit(state) => state.store_into(builder, context), @@ -66,6 +68,56 @@ impl<'a> Load<'a> for ShardState { } /// State of the single shard. +/// +/// # TLB scheme +/// +/// Old: +/// ```text +/// shard_state#9023afe2 +/// global_id:int32 +/// shard_id:ShardIdent +/// seq_no:uint32 vert_seq_no:# +/// gen_utime:uint32 gen_lt:uint64 +/// min_ref_mc_seqno:uint32 +/// out_msg_queue_info:^OutMsgQueueInfo +/// before_split:(## 1) +/// accounts:^ShardAccounts +/// ^[ +/// overload_history:uint64 +/// underload_history:uint64 +/// total_balance:CurrencyCollection +/// total_validator_fees:CurrencyCollection +/// libraries:(HashmapE 256 LibDescr) +/// master_ref:(Maybe BlkMasterInfo) +/// ] +/// custom:(Maybe ^McStateExtra) +/// = ShardStateUnsplit; +/// ``` +/// +/// New: +/// ```text +/// shard_state#9023aeee +/// global_id:int32 +/// shard_id:ShardIdent +/// seq_no:uint32 vert_seq_no:# +/// gen_utime:uint32 +/// gen_utime_ms:uint16 +/// gen_lt:uint64 +/// min_ref_mc_seqno:uint32 +/// processed_upto:^ProcessedUptoInfo +/// before_split:(## 1) +/// accounts:^ShardAccounts +/// ^[ +/// overload_history:uint64 +/// underload_history:uint64 +/// total_balance:CurrencyCollection +/// total_validator_fees:CurrencyCollection +/// libraries:(HashmapE 256 LibDescr) +/// master_ref:(Maybe BlkMasterInfo) +/// ] +/// custom:(Maybe ^McStateExtra) +/// = ShardStateUnsplit; +/// ``` #[derive(Debug, Clone, Eq, PartialEq)] pub struct ShardStateUnsplit { /// Global network id. @@ -205,7 +257,7 @@ impl Store for ShardStateUnsplit { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let child_cell = { let mut builder = CellBuilder::new(); @@ -348,11 +400,11 @@ pub struct LibDescr { } impl Store for LibDescr { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { ok!(builder.store_small_uint(0, 2)); ok!(builder.store_reference(self.lib.clone())); match self.publishers.root() { - Some(root) => builder.store_reference(root.clone()), + Some(root) => builder.store_slice(root.as_slice_allow_pruned()), None => Err(Error::InvalidData), } } @@ -365,73 +417,188 @@ impl<'a> Load<'a> for LibDescr { } Ok(Self { lib: ok!(slice.load_reference_cloned()), - publishers: ok!(Dict::load_from_root_ext(slice, &mut Cell::empty_context())), + publishers: ok!(Dict::load_from_root_ext(slice, Cell::empty_context())), }) } } -/// Processed up to info for externals and internals. +/// Processed upto info for externals/internals +/// and messages execution params. +/// +/// # TLB scheme +/// +/// ```text +/// processedUptoInfo#00 +/// partitions:(HashmapE 16 ProcessedUptoPartition) +/// msgs_exec_params:(Maybe ^MsgsExecutionParams) +/// = ProcessedUptoInfo; +/// ``` #[cfg(feature = "tycho")] #[derive(Debug, Default, Clone, Store, Load)] +#[tlb(tag = "#00")] pub struct ProcessedUptoInfo { - /// Externals processed up to point and range - /// to reproduce last messages set - /// (if it was not fully processed in prev block collation). - pub externals: Option, - /// Internals processed up to points and ranges by shards - /// to reproduce last messages set - /// (if it was not fully processed in prev block collation). - pub internals: Dict, - /// Offset of processed messages from last set. - /// Will be `!=0` if th set was not fully processed in prev block collation. - pub processed_offset: u32, + /// We split messages by partitions. + /// Main partition 0 and others. + pub partitions: Dict, + + /// Actual messages execution params used for collated block. + /// They help to refill messages buffers on sync/restart and + /// process remaning messages in queues with previous params + /// before switching to a new params version. + pub msgs_exec_params: Option>, } -/// Describes the processed up to point and range of externals -/// that we should read to reproduce the same messages set -/// on which the previous block collation stopped: -/// from message in FROM achor to message in TO anchor. +/// Processed up to info for externals and internals in one partition. /// -/// If last read messages set was fully processed then -/// will be `processed_to == read_to`. -/// So we do not need to reproduce the last messages set -/// and we can continue to read externals from `read_to`. +/// # TLB scheme +/// +/// ```text +/// processedUptoPartition#00 +/// externals:ExternalsProcessedUpto +/// internals:InternalsProcessedUpto +/// = ProcessedUptoPartition +/// ``` #[cfg(feature = "tycho")] -#[derive(Debug, Clone, Store, Load)] +#[derive(Debug, Default, Clone, Store, Load)] +#[tlb(tag = "#00")] +pub struct ProcessedUptoPartition { + /// Externals read range and processed to info. + pub externals: ExternalsProcessedUpto, + + /// Internals read ranges and processed to info. + pub internals: InternalsProcessedUpto, +} + +/// Describes the processed to point and ranges of externals +/// that we should read to reproduce the same messages buffer +/// on which the previous block collation stopped. +/// +/// # TLB scheme +/// +/// ```text +/// externalsProcessedUpto#00 +/// processed_to_anchor_id:uint32 +/// processed_to_msgs_offset:uint64 +/// ranges:(HashmapE 32 ExternalsRange) +/// = ExternalsProcessedUpto; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Default, Clone, Store, Load)] +#[tlb(tag = "#00")] pub struct ExternalsProcessedUpto { - /// Externals processed up to (anchor, len). - /// Means that all externals upto this point + /// Externals processed to (anchor id, msgs offset). + /// All externals up to this point /// already processed during previous blocks collations. - /// - /// Needs to read externals from this point to reproduce messages set for collation. pub processed_to: (u32, u64), - /// Needs to read externals to this point to reproduce messages set for collation. - pub read_to: (u32, u64), + + /// Externals read ranges map by block seqno. + pub ranges: Dict, } -/// Describes the processed up to point and range of internals -/// that we should read from shard to reproduce the same messages set -/// on which the previous block collation stopped: -/// from message LT_HASH to message LT_HASH. +/// Describes externals read range. /// -/// If last read messages set was fully processed then -/// will be -/// ``` -/// processed_to_msg == read_to_msg +/// # TLB scheme +/// +/// ```text +/// externalsRange#00 +/// from_anchor_id:uint32 +/// from_msgs_offset:uint64 +/// to_anchor_id:uint32 +/// to_msgs_offset:uint64 +/// chain_time:uint64 +/// skip_offset:uint32 +/// processed_offset:uint32 +/// = ExternalsRange; /// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Default, Clone, Store, Load)] +#[tlb(tag = "#00")] +pub struct ExternalsRange { + /// From mempool anchor id and msgs offset. + pub from: (u32, u64), + /// To mempool anchor id and msgs offset. + pub to: (u32, u64), + + /// Chain time of the block when range was read. + pub chain_time: u64, + + /// Skip offset before collecting messages from this range. + /// Because we should collect from others. + pub skip_offset: u32, + /// How many times externals messages were collected from all ranges. + /// Every range contains offset that was reached when range was the last. + /// So the current last range contains the actual offset. + pub processed_offset: u32, +} + +/// Describes the processed to point and ranges of internals +/// that we should read to reproduce the same messages buffer +/// on which the previous block collation stopped. +/// +/// # TLB scheme /// -/// So we do not need to reproduce the last messages set -/// and we can continue to read internals from -/// `read_to_msg_lt` and `read_to_msg_hash`. +/// ```text +/// internalsProcessedUpto#00 +/// processed_to:(HashmapE 96 ProcessedUpto) +/// ranges:(HashmapE 32 InternalsRange) +/// = InternalsProcessedUpto; +/// ``` #[cfg(feature = "tycho")] -#[derive(Debug, Clone, Store, Load)] +#[derive(Debug, Default, Clone, Store, Load)] +#[tlb(tag = "#00")] pub struct InternalsProcessedUpto { - /// Internals processed up to message (LT, Hash). - /// All internals upto this point + /// Internals processed to (LT, HASH) by source shards. + /// All internals up to this point /// already processed during previous blocks collations. - /// - /// Needs to read internals from this point to reproduce messages set for collation. - pub processed_to_msg: (u64, HashBytes), - /// Needs to read internals to this point to reproduce messages set for collation (LT, Hash). - pub read_to_msg: (u64, HashBytes), + pub processed_to: Dict, + + /// Internals read ranges map by block seqno. + pub ranges: Dict, +} + +/// Describes internals read range. +/// +/// # TLB scheme +/// +/// ```text +/// internalsRange#00 +/// skip_offset:uint32 +/// processed_offset:uint32 +/// shards:(HashmapE 96 ShardRange) +/// = InternalsRange; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Default, Clone, Store, Load)] +#[tlb(tag = "#00")] +pub struct InternalsRange { + /// Skip offset before collecting messages from this range. + /// Because we should collect from others. + pub skip_offset: u32, + /// How many times internal messages were collected from all ranges. + /// Every range contains offset that was reached when range was the last. + /// So the current last range contains the actual offset. + pub processed_offset: u32, + + /// Internals read ranges by source shards. + pub shards: Dict, +} + +/// Describes internals read range from one shard. +/// +/// # TLB scheme +/// +/// ```text +/// shardRange#00 +/// from:uint64 +/// to:uint64 +/// = ShardRange; +/// ``` +#[cfg(feature = "tycho")] +#[derive(Debug, Default, Clone, Store, Load)] +#[tlb(tag = "#00")] +pub struct ShardRange { + /// From LT. + pub from: u64, + /// To LT. + pub to: u64, } diff --git a/src/models/shard/shard_accounts.rs b/src/models/shard/shard_accounts.rs index 20f97519..d9ec542d 100644 --- a/src/models/shard/shard_accounts.rs +++ b/src/models/shard/shard_accounts.rs @@ -32,7 +32,7 @@ impl AugDictExtra for DepthBalanceInfo { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - cx: &mut dyn CellContext, + cx: &dyn CellContext, ) -> Result<(), Error> { let left = ok!(Self::load_from(left)); let right = ok!(Self::load_from(right)); @@ -48,7 +48,7 @@ impl Store for DepthBalanceInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { if !self.is_valid() { return Err(Error::IntOverflow); diff --git a/src/models/shard/shard_extra.rs b/src/models/shard/shard_extra.rs index be09bc5b..4d6fe49c 100644 --- a/src/models/shard/shard_extra.rs +++ b/src/models/shard/shard_extra.rs @@ -8,6 +8,28 @@ use crate::models::config::BlockchainConfig; use crate::models::currency::CurrencyCollection; /// Additional content for masterchain state. +/// +/// # TLB scheme +/// +/// ```text +/// copyleft_rewards#_ counters:(HashmapE 256 Grams) = CopyleftRewards; +/// +/// masterchain_state_extra#cc26 +/// shard_hashes:ShardHashes +/// config:ConfigParams +/// ^[ +/// flags:(## 16) { flags <= 7 } +/// validator_info:ValidatorInfo +/// prev_blocks:OldMcBlocksInfo +/// after_key_block:Bool +/// last_key_block:(Maybe ExtBlkRef) +/// block_create_stats:(flags . 0)?BlockCreateStats +/// copyleft_rewards:(flags . 1)?CopyleftRewards +/// consensus_info:(flags . 2)?ConsensusInfo +/// ] +/// global_balance:CurrencyCollection +/// = McStateExtra; +/// ``` #[derive(Debug, Clone)] pub struct McStateExtra { /// A tree of the most recent descriptions for all currently existing shards @@ -17,6 +39,9 @@ pub struct McStateExtra { pub config: BlockchainConfig, /// Brief validator info. pub validator_info: ValidatorInfo, + /// Brief consensus bounds info. + #[cfg(feature = "tycho")] + pub consensus_info: ConsensusInfo, /// A dictionary with previous masterchain blocks. pub prev_blocks: AugDict, /// Whether this state was produced after the key block. @@ -40,11 +65,19 @@ impl Store for McStateExtra { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { - let flags = ((!self.copyleft_rewards.is_empty() as u16) << 1) + #[allow(unused_mut)] + let mut flags = ((!self.copyleft_rewards.is_empty() as u16) << 1) | (self.block_create_stats.is_some() as u16); + #[cfg(feature = "tycho")] + let has_consensus_info = { + let non_default_info = !self.consensus_info.is_zerostate(); + flags |= (non_default_info as u16) << 2; + non_default_info + }; + let cell = { let mut builder = CellBuilder::new(); ok!(builder.store_u16(flags)); @@ -62,6 +95,11 @@ impl Store for McStateExtra { ok!(self.copyleft_rewards.store_into(&mut builder, context)); } + #[cfg(feature = "tycho")] + if has_consensus_info { + ok!(self.consensus_info.store_into(&mut builder, context)); + } + ok!(builder.build_ext(context)) }; @@ -87,7 +125,12 @@ impl<'a> Load<'a> for McStateExtra { let child_slice = &mut ok!(slice.load_reference_as_slice()); let flags = ok!(child_slice.load_u16()); - if flags >> 2 != 0 { + #[cfg(not(feature = "tycho"))] + const RESERVED_BITS: usize = 2; + #[cfg(feature = "tycho")] + const RESERVED_BITS: usize = 3; + + if flags >> RESERVED_BITS != 0 { return Err(Error::InvalidData); } @@ -98,7 +141,7 @@ impl<'a> Load<'a> for McStateExtra { prev_blocks: ok!(AugDict::load_from(child_slice)), after_key_block: ok!(child_slice.load_bit()), last_key_block: ok!(Option::::load_from(child_slice)), - block_create_stats: if flags & 0b01 != 0 { + block_create_stats: if flags & 0b001 != 0 { if ok!(child_slice.load_u8()) != Self::BLOCK_STATS_TAG { return Err(Error::InvalidTag); } @@ -107,11 +150,17 @@ impl<'a> Load<'a> for McStateExtra { None }, global_balance: ok!(CurrencyCollection::load_from(slice)), - copyleft_rewards: if flags & 0b10 != 0 { + copyleft_rewards: if flags & 0b010 != 0 { ok!(Dict::load_from(child_slice)) } else { Dict::new() }, + #[cfg(feature = "tycho")] + consensus_info: if flags & 0b100 != 0 { + ok!(ConsensusInfo::load_from(child_slice)) + } else { + ConsensusInfo::ZEROSTATE + }, }) } } @@ -137,6 +186,79 @@ pub struct ValidatorBaseInfo { pub catchain_seqno: u32, } +/// Brief consensus bounds info. +/// +/// ```text +/// consensus_info#_ +/// vset_switch_round:uint32 +/// prev_vset_switch_round:uint32 +/// genesis_info:GenesisInfo +/// prev_shuffle_mc_validators:Bool +/// = ConsensusInfo; +/// ``` +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Store, Load)] +pub struct ConsensusInfo { + /// The most recent round from which the mempool session starts. + pub vset_switch_round: u32, + + /// The round from which the previous mempool session was started. + pub prev_vset_switch_round: u32, + + /// The last applied genesis params + /// Can be changed in node configs to start new session + pub genesis_info: GenesisInfo, + + /// Previous state of the `shuffle_mc_validators` flags. + pub prev_shuffle_mc_validators: bool, +} + +/// Brief genesis info. +/// +/// ```text +/// genesis_info#_ +/// start_round:uint32 +/// genesis_millis:uint64 +/// = GenesisInfo; +/// ``` +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Store, Load)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GenesisInfo { + /// Unaligned genesis round that corresponds to the last (maybe partially) processed anchor + /// from the last master chain block signed by majority. + /// Aligned (a bit increased or left unchanged) genesis round affects the overlay id. + pub start_round: u32, + + /// Timestamp in milliseconds to include into mempool genesis point. + /// Newly produced points are required to have greater value. + /// Unchanged value affects the overlay id. + pub genesis_millis: u64, +} + +impl GenesisInfo { + /// If genesis overrides the other, then it will be used to start a blank new mempool session + pub fn overrides(&self, other: &Self) -> bool { + self.start_round >= other.start_round && self.genesis_millis > other.genesis_millis + } +} + +impl ConsensusInfo { + /// Initial consensus info state. + pub const ZEROSTATE: Self = Self { + vset_switch_round: 0, + prev_vset_switch_round: 0, + genesis_info: GenesisInfo { + start_round: 0, + genesis_millis: 0, + }, + prev_shuffle_mc_validators: false, + }; + + /// Returns whether this info corresponds to the zerostate info. + pub fn is_zerostate(&self) -> bool { + self == &Self::ZEROSTATE + } +} + /// Entry value for the [`OldMcBlocksInfo`] dictionary. #[derive(Debug, Clone, Eq, PartialEq, Store, Load)] pub struct KeyBlockRef { @@ -160,7 +282,7 @@ impl AugDictExtra for KeyMaxLt { left: &mut CellSlice, right: &mut CellSlice, b: &mut CellBuilder, - cx: &mut dyn CellContext, + cx: &dyn CellContext, ) -> Result<(), Error> { let left = ok!(Self::load_from(left)); let right = ok!(Self::load_from(right)); diff --git a/src/models/transaction/mod.rs b/src/models/transaction/mod.rs index b4c248e4..2ed0d47e 100644 --- a/src/models/transaction/mod.rs +++ b/src/models/transaction/mod.rs @@ -99,7 +99,7 @@ mod serde_in_msg { None => serializer.serialize_none(), } } else { - crate::boc::OptionBoc::serialize(in_msg, serializer) + crate::boc::Boc::serialize(in_msg, serializer) } } @@ -119,7 +119,7 @@ mod serde_in_msg { None => Ok(None), } } else { - crate::boc::OptionBoc::deserialize(deserializer) + crate::boc::Boc::deserialize(deserializer) } } } @@ -162,7 +162,7 @@ mod serde_out_msgs { ahash::HashMap::::deserialize(deserializer) ); - let cx = &mut Cell::empty_context(); + let cx = Cell::empty_context(); let mut dict = Dict::new(); for (key, value) in &messages { let cell = ok!(CellBuilder::build_from(value).map_err(Error::custom)); @@ -215,7 +215,7 @@ impl Store for Transaction { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let messages = { let mut builder = CellBuilder::new(); @@ -288,7 +288,7 @@ impl Store for TxInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Ordinary(info) => { @@ -358,7 +358,7 @@ impl Store for OrdinaryTxInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let action_phase = match &self.action_phase { Some(action_phase) => { @@ -422,7 +422,7 @@ impl Store for TickTockTxInfo { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let action_phase = match &self.action_phase { Some(action_phase) => { @@ -477,7 +477,7 @@ pub enum TickTock { impl Store for TickTock { #[inline] - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_bit(*self == Self::Tock) } } diff --git a/src/models/transaction/phases.rs b/src/models/transaction/phases.rs index 1000d45e..ff4b5c95 100644 --- a/src/models/transaction/phases.rs +++ b/src/models/transaction/phases.rs @@ -50,7 +50,7 @@ impl Store for ComputePhase { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::Skipped(phase) => { @@ -167,7 +167,7 @@ pub enum ComputePhaseSkipReason { } impl Store for ComputePhaseSkipReason { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let (tag, bits) = match self { Self::NoState => (0b00, 2), Self::BadState => (0b01, 2), @@ -239,7 +239,7 @@ impl Store for ActionPhase { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { let flags = ((self.success as u8) << 2) | ((self.valid as u8) << 1) | self.no_funds as u8; let counts = ((self.total_actions as u64) << 48) @@ -311,7 +311,7 @@ impl Store for BouncePhase { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::NegativeFunds => builder.store_small_uint(0b00, 2), @@ -380,7 +380,7 @@ pub enum AccountStatusChange { } impl Store for AccountStatusChange { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { if *self == Self::Unchanged { builder.store_bit_zero() } else { diff --git a/src/models/vm/out_actions.rs b/src/models/vm/out_actions.rs index d01cc305..8091b079 100644 --- a/src/models/vm/out_actions.rs +++ b/src/models/vm/out_actions.rs @@ -18,7 +18,7 @@ impl<'a> OutActionsRevIter<'a> { } } -impl<'a> Iterator for OutActionsRevIter<'a> { +impl Iterator for OutActionsRevIter<'_> { type Item = Result; fn next(&mut self) -> Option { @@ -54,6 +54,8 @@ bitflags! { /// Any errors arising while processing this message during /// the action phase should be ignored. const IGNORE_ERROR = 2; + /// Causes bounce if action fails. + const BOUNCE_ON_ERROR = 16; /// The current account must be destroyed if its resulting balance is zero. const DELETE_IF_EMPTY = 32; /// Message will carry all the remaining value of the inbound message @@ -67,7 +69,7 @@ bitflags! { } impl Store for SendMsgFlags { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_u8(self.bits()) } } @@ -93,11 +95,13 @@ bitflags! { const WITH_ORIGINAL_BALANCE = 4; /// `x = −x` before performing any further action. const REVERSE = 8; + /// Causes bounce if action fails. + const BOUNCE_ON_ERROR = 16; } } impl Store for ReserveCurrencyFlags { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_u8(self.bits()) } } @@ -109,28 +113,29 @@ impl<'a> Load<'a> for ReserveCurrencyFlags { } } -/// Mode flags for `ChangeLibrary` output action. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[repr(u8)] -pub enum ChangeLibraryMode { - /// Remove library. - Remove = 0, - /// Add private library. - AddPrivate = 1, - /// Add public library. - AddPublic = 2, -} - -impl TryFrom for ChangeLibraryMode { - type Error = Error; - - fn try_from(value: u8) -> Result { - Ok(match value { - 0 => Self::Remove, - 1 => Self::AddPrivate, - 2 => Self::AddPublic, - _ => return Err(Error::InvalidData), - }) +bitflags! { + /// Mode flags for `ChangeLibrary` output action. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ChangeLibraryMode: u8 { + /// Remove library. + /// + /// NOTE: Exclusive with [`ADD_PUBLIC`] or [`ADD_PRIVATE`]. + /// [`ADD_PUBLIC`]: ChangeLibraryMode::ADD_PUBLIC + /// [`ADD_PRIVATE`]: ChangeLibraryMode::ADD_PRIVATE + const REMOVE = 0; + /// Add private library. + /// + /// NOTE: Exclusive with [`ADD_PUBLIC`]. + /// [`ADD_PUBLIC`]: ChangeLibraryMode::ADD_PUBLIC + const ADD_PRIVATE = 1; + /// Add public library. + /// + /// NOTE: Exclusive with [`ADD_PRIVATE`]. + /// + /// [`ADD_PRIVATE`]: ChangeLibraryMode::ADD_PRIVATE + const ADD_PUBLIC = 2; + /// Causes bounce if action fails. + const BOUNCE_ON_ERROR = 16; } } @@ -182,18 +187,23 @@ pub enum OutAction { } impl OutAction { - const TAG_SEND_MSG: u32 = 0x0ec3c86d; - const TAG_SET_CODE: u32 = 0xad4de08e; - const TAG_RESERVE: u32 = 0x36e6b809; - const TAG_CHANGE_LIB: u32 = 0x26fa1dd4; - const TAG_COPYLEFT: u32 = 0x24486f7a; + /// Tag for [`OutAction::SendMsg`]. + pub const TAG_SEND_MSG: u32 = 0x0ec3c86d; + /// Tag for [`OutAction::SetCode`]. + pub const TAG_SET_CODE: u32 = 0xad4de08e; + /// Tag for [`OutAction::ReserveCurrency`]. + pub const TAG_RESERVE: u32 = 0x36e6b809; + /// Tag for [`OutAction::ChangeLibrary`]. + pub const TAG_CHANGE_LIB: u32 = 0x26fa1dd4; + /// Tag for [`OutAction::CopyLeft`]. + pub const TAG_COPYLEFT: u32 = 0x24486f7a; } impl Store for OutAction { fn store_into( &self, builder: &mut CellBuilder, - context: &mut dyn CellContext, + context: &dyn CellContext, ) -> Result<(), Error> { match self { Self::SendMsg { mode, out_msg } => { @@ -214,11 +224,11 @@ impl Store for OutAction { ok!(builder.store_u32(Self::TAG_CHANGE_LIB)); match lib { LibRef::Hash(hash) => { - ok!(builder.store_u8((*mode as u8) << 1)); + ok!(builder.store_u8(mode.bits() << 1)); builder.store_u256(hash) } LibRef::Cell(cell) => { - ok!(builder.store_u8(((*mode as u8) << 1) | 1)); + ok!(builder.store_u8((mode.bits() << 1) | 1)); builder.store_reference(cell.clone()) } } @@ -249,7 +259,7 @@ impl<'a> Load<'a> for OutAction { }, Self::TAG_CHANGE_LIB => { let flags = ok!(slice.load_u8()); - let mode = ok!(ChangeLibraryMode::try_from(flags >> 1)); + let mode = ChangeLibraryMode::from_bits_retain(flags >> 1); Self::ChangeLibrary { mode, lib: if flags & 1 == 0 { diff --git a/src/num/mod.rs b/src/num/mod.rs index b4210935..b453d900 100644 --- a/src/num/mod.rs +++ b/src/num/mod.rs @@ -382,8 +382,43 @@ macro_rules! impl_var_uints { } } + /// Saturating integer addition. Computes `self + rhs`, + /// saturating at the numeric bounds instead of overflowing. + #[inline] + #[must_use] + pub const fn saturating_add(self, rhs: Self) -> Self { + match self.0.checked_add(rhs.0) { + Some(value) if value <= Self::MAX.0 => $ident(value), + _ => Self::MAX, + } + } + + /// Saturating integer addition. Computes `self - rhs`, + /// saturating at the numeric bounds instead of overflowing. + #[inline] + #[must_use] + pub const fn saturating_sub(self, rhs: Self) -> Self { + match self.0.checked_sub(rhs.0) { + Some(value) if value <= Self::MAX.0 => $ident(value), + Some(_) => Self::MAX, + None => Self::ZERO, + } + } + + /// Saturating integer multiplication. Computes `self * rhs`, + /// returning `None` if overflow occurred. + #[inline] + #[must_use] + pub const fn saturating_mul(self, rhs: Self) -> Self { + match self.0.checked_mul(rhs.0) { + Some(value) if value <= Self::MAX.0 => $ident(value), + _ => Self::MAX, + } + } + /// Checked integer addition. Computes `self + rhs`, returning `None` if overflow occurred. #[inline] + #[must_use] pub const fn checked_add(self, rhs: Self) -> Option { match self.0.checked_add(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), @@ -393,6 +428,7 @@ macro_rules! impl_var_uints { /// Checked integer subtraction. Computes `self - rhs`, returning `None` if overflow occurred. #[inline] + #[must_use] pub const fn checked_sub(self, rhs: Self) -> Option { match self.0.checked_sub(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), @@ -402,6 +438,7 @@ macro_rules! impl_var_uints { /// Checked integer multiplication. Computes `self * rhs`, returning `None` if overflow occurred. #[inline] + #[must_use] pub const fn checked_mul(self, rhs: Self) -> Option { match self.0.checked_mul(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), @@ -412,12 +449,57 @@ macro_rules! impl_var_uints { /// Checked integer division. Computes `self / rhs`, returning None if `rhs == 0` /// or overflow occurred. #[inline] + #[must_use] pub const fn checked_div(self, rhs: Self) -> Option { match self.0.checked_div(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), _ => None, } } + + /// Tries to add an other value to the current one. + pub fn try_add_assign(&mut self, other: Self) -> Result<(), Error> { + match self.checked_add(other) { + Some(new_value) => { + *self = new_value; + Ok(()) + }, + None => Err(Error::IntOverflow), + } + } + + /// Tries to subtract an other value from the current one. + pub fn try_sub_assign(&mut self, other: Self) -> Result<(), Error> { + match self.checked_sub(other) { + Some(new_value) => { + *self = new_value; + Ok(()) + }, + None => Err(Error::IntOverflow), + } + } + + /// Tries to multiply the current value by the other value. + pub fn try_mul_assign(&mut self, other: Self) -> Result<(), Error> { + match self.checked_mul(other) { + Some(new_value) => { + *self = new_value; + Ok(()) + }, + None => Err(Error::IntOverflow), + } + } + + /// Tries to divice the current value by the other value. + pub fn try_div_assign(&mut self, other: Self) -> Result<(), Error> { + match self.checked_div(other) { + Some(new_value) => { + *self = new_value; + Ok(()) + }, + None => Err(Error::IntOverflow), + } + } } impl ExactSize for $ident { @@ -486,7 +568,7 @@ impl<'de> serde::Deserialize<'de> for Tokens { struct TokensVisitor; - impl<'de> Visitor<'de> for TokensVisitor { + impl Visitor<'_> for TokensVisitor { type Value = u128; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -516,7 +598,7 @@ impl<'de> serde::Deserialize<'de> for Tokens { } impl Store for VarUint24 { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let bytes = (4 - self.0.leading_zeros() / 8) as u8; let bits = bytes as u16 * 8; @@ -540,7 +622,7 @@ impl<'a> Load<'a> for VarUint24 { } impl Store for VarUint56 { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let bytes = (8 - self.0.leading_zeros() / 8) as u8; let bits = bytes as u16 * 8; @@ -564,7 +646,7 @@ impl<'a> Load<'a> for VarUint56 { } impl Store for Tokens { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let bytes = (16 - self.0.leading_zeros() / 8) as u8; let bits = bytes as u16 * 8; @@ -640,8 +722,43 @@ macro_rules! impl_small_uints { self.0 <= Self::MAX.0 } + /// Saturating integer addition. Computes `self + rhs`, + /// saturating at the numeric bounds instead of overflowing. + #[inline] + #[must_use] + pub const fn saturating_add(self, rhs: Self) -> Self { + match self.0.checked_add(rhs.0) { + Some(value) if value <= Self::MAX.0 => $ident(value), + _ => Self::MAX, + } + } + + /// Saturating integer addition. Computes `self - rhs`, + /// saturating at the numeric bounds instead of overflowing. + #[inline] + #[must_use] + pub const fn saturating_sub(self, rhs: Self) -> Self { + match self.0.checked_sub(rhs.0) { + Some(value) if value <= Self::MAX.0 => $ident(value), + Some(_) => Self::MAX, + None => Self::MIN, + } + } + + /// Saturating integer multiplication. Computes `self * rhs`, + /// returning `None` if overflow occurred. + #[inline] + #[must_use] + pub const fn saturating_mul(self, rhs: Self) -> Self { + match self.0.checked_mul(rhs.0) { + Some(value) if value <= Self::MAX.0 => $ident(value), + _ => Self::MAX, + } + } + /// Checked integer addition. Computes `self + rhs`, returning `None` if overflow occurred. #[inline] + #[must_use] pub const fn checked_add(self, rhs: Self) -> Option { match self.0.checked_add(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), @@ -651,6 +768,7 @@ macro_rules! impl_small_uints { /// Checked integer subtraction. Computes `self - rhs`, returning `None` if overflow occurred. #[inline] + #[must_use] pub const fn checked_sub(self, rhs: Self) -> Option { match self.0.checked_sub(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), @@ -660,6 +778,7 @@ macro_rules! impl_small_uints { /// Checked integer multiplication. Computes `self * rhs`, returning `None` if overflow occurred. #[inline] + #[must_use] pub const fn checked_mul(self, rhs: Self) -> Option { match self.0.checked_mul(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), @@ -670,6 +789,7 @@ macro_rules! impl_small_uints { /// Checked integer division. Computes `self / rhs`, returning None if `rhs == 0` /// or overflow occurred. #[inline] + #[must_use] pub const fn checked_div(self, rhs: Self) -> Option { match self.0.checked_div(rhs.0) { Some(value) if value <= Self::MAX.0 => Some($ident(value)), @@ -689,7 +809,7 @@ macro_rules! impl_small_uints { fn store_into( &self, builder: &mut CellBuilder, - _: &mut dyn CellContext + _: &dyn CellContext ) -> Result<(), Error> { if !self.is_valid() { return Err(Error::IntOverflow); @@ -793,7 +913,7 @@ impl ExactSize for SplitDepth { } impl Store for SplitDepth { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { builder.store_small_uint(self.0.get(), Self::BITS) } } @@ -936,7 +1056,7 @@ mod tests { macro_rules! impl_serialization_tests { ($ident:ident, $max_bits:literal) => { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); for i in 0..$max_bits { let value = $ident::ONE << i; @@ -955,7 +1075,7 @@ mod tests { macro_rules! impl_deserialization_tests { ($ident:ident, $max_bits:literal, $value:literal) => { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); let mut value = $ident::new($value); for _ in 0..=$max_bits { @@ -973,7 +1093,7 @@ mod tests { macro_rules! impl_fixed_len_serialization_tests { ($ident:ident, $max_bits:literal) => { - let context = &mut Cell::empty_context(); + let context = Cell::empty_context(); for i in 0..$max_bits { let value = $ident::ONE << i; @@ -1055,4 +1175,41 @@ mod tests { fn tokens_deserialization() { impl_deserialization_tests!(Tokens, 120, 0xabcdef89abcdefdeadbeeffafacafe); } + + fn _num_must_use() { + #[expect(unused_must_use)] + { + Uint9::new(10).checked_add(Uint9::ZERO); + } + + #[expect(unused_must_use)] + { + Uint12::new(10).checked_add(Uint12::ZERO); + } + + #[expect(unused_must_use)] + { + Uint15::new(10).checked_add(Uint15::ZERO); + } + + #[expect(unused_must_use)] + { + VarUint24::new(10).checked_add(VarUint24::ZERO); + } + + #[expect(unused_must_use)] + { + VarUint56::new(10).checked_add(VarUint56::ZERO); + } + + #[expect(unused_must_use)] + { + Tokens::new(10).checked_add(Tokens::ZERO); + } + + #[expect(unused_must_use)] + { + VarUint248::new(10).checked_add(&VarUint248::ZERO); + } + } } diff --git a/src/num/varuint248.rs b/src/num/varuint248.rs index 54a39b67..b1a4c655 100644 --- a/src/num/varuint248.rs +++ b/src/num/varuint248.rs @@ -101,36 +101,89 @@ impl VarUint248 { } } + /// Saturating integer addition. Computes `self + rhs`, + /// saturating at the numeric bounds instead of overflowing. + #[must_use] + pub const fn saturating_add(self, rhs: &Self) -> Self { + match self.checked_add(rhs) { + Some(value) => value, + None => Self::MAX, + } + } + + /// Saturating integer addition. Computes `self - rhs`, + /// saturating at the numeric bounds instead of overflowing. + #[must_use] + pub const fn saturating_sub(self, rhs: &Self) -> Self { + let (lo, carry_lo) = self.low().overflowing_sub(*rhs.low()); + let (hi, carry_c) = self.high().overflowing_sub(carry_lo as _); + let (hi, carry_hi) = hi.overflowing_sub(*rhs.high()); + + if carry_c || carry_hi { + return Self::ZERO; + } + + let res = Self::from_words(hi, lo); + if res.is_valid() { + res + } else { + Self::MAX + } + } + + /// Saturating integer multiplication. Computes `self * rhs`, + /// returning `None` if overflow occurred. + #[must_use] + pub fn saturating_mul(self, rhs: &Self) -> Self { + match self.checked_mul(rhs) { + Some(value) => value, + None => Self::MAX, + } + } + /// Checked integer addition. Computes `self + rhs`, /// returning `None` if overflow occurred. + #[must_use] pub const fn checked_add(&self, rhs: &Self) -> Option { let (lo, carry_lo) = self.low().overflowing_add(*rhs.low()); let (hi, carry_c) = self.high().overflowing_add(carry_lo as _); let (hi, carry_hi) = hi.overflowing_add(*rhs.high()); - if carry_c || carry_hi || !self.is_valid() { - None + if carry_c || carry_hi { + return None; + } + + let res = Self::from_words(hi, lo); + if res.is_valid() { + Some(res) } else { - Some(Self::from_words(hi, lo)) + None } } /// Checked integer subtraction. Computes `self - rhs`, /// returning `None` if overflow occurred. + #[must_use] pub const fn checked_sub(&self, rhs: &Self) -> Option { let (lo, carry_lo) = self.low().overflowing_sub(*rhs.low()); let (hi, carry_c) = self.high().overflowing_sub(carry_lo as _); let (hi, carry_hi) = hi.overflowing_sub(*rhs.high()); - if carry_c || carry_hi || !self.is_valid() { - None + if carry_c || carry_hi { + return None; + } + + let res = Self::from_words(hi, lo); + if res.is_valid() { + Some(res) } else { - Some(Self::from_words(hi, lo)) + None } } /// Checked integer multiplication. Computes `self * rhs`, /// returning `None` if overflow occurred. + #[must_use] pub fn checked_mul(&self, rhs: &Self) -> Option { let mut res = umulddi3(self.low(), rhs.low()); @@ -240,7 +293,7 @@ impl PartialOrd for VarUint248 { } impl Store for VarUint248 { - fn store_into(&self, builder: &mut CellBuilder, _: &mut dyn CellContext) -> Result<(), Error> { + fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> { let bytes = (32 - self.leading_zeros() / 8) as u8; let mut bits = bytes as u16 * 8; @@ -302,7 +355,7 @@ impl<'de> serde::Deserialize<'de> for VarUint248 { struct VarUint248Visitor; - impl<'de> Visitor<'de> for VarUint248Visitor { + impl Visitor<'_> for VarUint248Visitor { type Value = VarUint248; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -1236,6 +1289,21 @@ mod tests { assert!(!VarUint248::from_words(u128::MAX >> 7, u128::MAX).is_valid()); assert!(!VarUint248::from_words(u128::MAX, u128::MAX).is_valid()); + + assert_eq!( + VarUint248::new(123).saturating_sub(&VarUint248::new(321)), + VarUint248::ZERO + ); + + assert_eq!( + VarUint248::from_words(u128::MAX, u128::MAX).saturating_sub(&VarUint248::new(1)), + VarUint248::MAX + ); + assert_eq!( + VarUint248::from_words(u128::MAX, u128::MAX) + .saturating_sub(&VarUint248::from_words(u128::MAX, u128::MAX)), + VarUint248::ZERO + ); } #[test] diff --git a/src/prelude.rs b/src/prelude.rs index f17f8f0c..835c0911 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -6,9 +6,6 @@ pub use crate::boc::{Boc, BocRepr}; pub use crate::cell::{ Cell, CellBuilder, CellContext, CellFamily, CellImpl, CellSlice, CellSliceParts, CellSliceRange, CellType, DynCell, EquivalentRepr, ExactSize, HashBytes, Load, Size, Store, - UsageTree, UsageTreeMode, + UsageTree, UsageTreeMode, WeakCell, }; pub use crate::dict::{AugDict, Dict, RawDict}; - -#[cfg(feature = "serde")] -pub use crate::boc::OptionBoc; diff --git a/src/util.rs b/src/util.rs index 9a72f067..7e1f31aa 100644 --- a/src/util.rs +++ b/src/util.rs @@ -94,6 +94,43 @@ pub(crate) fn decode_base64_slice>( decode_base64_slice_impl(data.as_ref(), target) } +#[cfg(any(feature = "base64", test))] +#[inline] +pub(crate) fn crc_16(data: &[u8]) -> u16 { + let mut crc: u32 = 0; + for c in data { + let t = c ^ ((crc >> 8) as u8); + crc = (CRC16_TABLE[t as usize] ^ ((crc << 8) as u16)) as u32; + } + crc as u16 +} + +#[cfg(any(feature = "base64", test))] +static CRC16_TABLE: [u16; 256] = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, + 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, + 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, + 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, + 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, + 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, + 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, + 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, + 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, + 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, + 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, +]; + /// Small on-stack vector of max length N. pub struct ArrayVec { inner: [MaybeUninit; N],