diff --git a/Cargo.lock b/Cargo.lock index 4081f6e76..f543c9b13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,11 +1118,11 @@ dependencies = [ [[package]] name = "ic-cdk" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "candid", - "ic-cdk-macros 0.14.0", + "ic-cdk-macros 0.15.0", "ic0 0.23.0", "rstest", "serde", @@ -1151,7 +1151,7 @@ dependencies = [ "escargot", "futures", "hex", - "ic-cdk 0.14.0", + "ic-cdk 0.15.0", "ic-cdk-timers", "lazy_static", "pocket-ic", @@ -1175,7 +1175,7 @@ dependencies = [ [[package]] name = "ic-cdk-macros" -version = "0.14.0" +version = "0.15.0" dependencies = [ "candid", "proc-macro2", @@ -1187,10 +1187,10 @@ dependencies = [ [[package]] name = "ic-cdk-timers" -version = "0.8.0" +version = "0.9.0" dependencies = [ "futures", - "ic-cdk 0.14.0", + "ic-cdk 0.15.0", "ic0 0.23.0", "serde", "serde_bytes", @@ -1204,7 +1204,7 @@ dependencies = [ "bincode", "candid", "hex", - "ic-cdk 0.14.0", + "ic-cdk 0.15.0", "serde", "serde_bytes", "serde_cbor", @@ -1213,12 +1213,12 @@ dependencies = [ [[package]] name = "ic-ledger-types" -version = "0.11.0" +version = "0.12.0" dependencies = [ "candid", "crc32fast", "hex", - "ic-cdk 0.14.0", + "ic-cdk 0.15.0", "serde", "serde_bytes", "sha2", diff --git a/Cargo.toml b/Cargo.toml index 14f536c9e..f17419d37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,8 @@ opt-level = 'z' [workspace.dependencies] ic0 = { path = "src/ic0", version = "0.23.0" } -ic-cdk = { path = "src/ic-cdk", version = "0.14.0"} -ic-cdk-timers = { path = "src/ic-cdk-timers", version = "0.8.0" } +ic-cdk = { path = "src/ic-cdk", version = "0.15.0" } +ic-cdk-timers = { path = "src/ic-cdk-timers", version = "0.9.0" } candid = "0.10.4" candid_parser = "0.1.4" diff --git a/README.md b/README.md index cff587ef6..6aff8e388 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ In Cargo.toml: crate-type = ["cdylib"] [dependencies] -ic-cdk = "0.12" +ic-cdk = "0.15" # Only necessary if you want to define Candid data types candid = "0.10" ``` diff --git a/e2e-tests/canisters/canister_info.rs b/e2e-tests/canisters/canister_info.rs index 354d7fa1c..9a4bc2078 100644 --- a/e2e-tests/canisters/canister_info.rs +++ b/e2e-tests/canisters/canister_info.rs @@ -69,6 +69,7 @@ async fn canister_lifecycle() -> Principal { memory_allocation: None, freezing_threshold: None, reserved_cycles_limit: None, + log_visibility: None, wasm_memory_limit: None, }, canister_id: canister_id.canister_id, diff --git a/e2e-tests/canisters/management_caller.rs b/e2e-tests/canisters/management_caller.rs index a5c1f53ca..db662d6b4 100644 --- a/e2e-tests/canisters/management_caller.rs +++ b/e2e-tests/canisters/management_caller.rs @@ -18,6 +18,7 @@ mod main { memory_allocation: Some(10000u16.into()), freezing_threshold: Some(u64::MAX.into()), reserved_cycles_limit: Some(u128::MAX.into()), + log_visibility: Some(LogVisibility::Public), wasm_memory_limit: Some((2u64.pow(48) - 1).into()), }), }; @@ -37,6 +38,10 @@ mod main { assert_eq!(definite_canister_setting.memory_allocation, 10000u16); assert_eq!(definite_canister_setting.freezing_threshold, u64::MAX); assert_eq!(definite_canister_setting.reserved_cycles_limit, u128::MAX); + assert_eq!( + definite_canister_setting.log_visibility, + LogVisibility::Public + ); assert_eq!( definite_canister_setting.wasm_memory_limit, 2u64.pow(48) - 1 @@ -72,6 +77,7 @@ mod main { mod provisional { use super::*; + use api::management_canister::main::LogVisibility; use ic_cdk::api::management_canister::provisional::*; #[update] @@ -82,6 +88,7 @@ mod provisional { memory_allocation: Some(10000u16.into()), freezing_threshold: Some(10000u16.into()), reserved_cycles_limit: Some(10000u16.into()), + log_visibility: Some(LogVisibility::Public), wasm_memory_limit: Some(10000u16.into()), }; let arg = ProvisionalCreateCanisterWithCyclesArgument { diff --git a/library/ic-ledger-types/CHANGELOG.md b/library/ic-ledger-types/CHANGELOG.md index 4f577b031..1a0d69ad9 100644 --- a/library/ic-ledger-types/CHANGELOG.md +++ b/library/ic-ledger-types/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.12.0] - 2024-07-01 + +### Changed + +- Upgrade `ic-cdk` to v0.15. + ## [0.11.0] - 2024-05-17 ### Changed diff --git a/library/ic-ledger-types/Cargo.toml b/library/ic-ledger-types/Cargo.toml index 0598de229..2e051ff20 100644 --- a/library/ic-ledger-types/Cargo.toml +++ b/library/ic-ledger-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic-ledger-types" -version = "0.11.0" +version = "0.12.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/src/ic-cdk-bindgen/CHANGELOG.md b/src/ic-cdk-bindgen/CHANGELOG.md index 6c1c2941a..6135674ed 100644 --- a/src/ic-cdk-bindgen/CHANGELOG.md +++ b/src/ic-cdk-bindgen/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +- Support canister setting `log_visibility`. + ### Changed - Refactor!: move Rust code generation logic from candid_parser. (#480) diff --git a/src/ic-cdk-macros/CHANGELOG.md b/src/ic-cdk-macros/CHANGELOG.md index 1393753d8..86df405c8 100644 --- a/src/ic-cdk-macros/CHANGELOG.md +++ b/src/ic-cdk-macros/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Fixed + +- `cargo build` should no longer give a confusing linkage error on Linux. + ## [0.13.2] - 2024-04-08 ### Changed diff --git a/src/ic-cdk-macros/Cargo.toml b/src/ic-cdk-macros/Cargo.toml index 22f66b642..7be3dc13e 100644 --- a/src/ic-cdk-macros/Cargo.toml +++ b/src/ic-cdk-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic-cdk-macros" -version = "0.14.0" # sync with ic-cdk +version = "0.15.0" # sync with ic-cdk authors.workspace = true edition.workspace = true license.workspace = true diff --git a/src/ic-cdk-macros/src/export.rs b/src/ic-cdk-macros/src/export.rs index 2141586c7..6d087d398 100644 --- a/src/ic-cdk-macros/src/export.rs +++ b/src/ic-cdk-macros/src/export.rs @@ -162,6 +162,7 @@ fn dfn_macro( } format!("canister_{method} {function_name}") }; + let host_compatible_name = export_name.replace(' ', ".").replace(['-', '<', '>'], "_"); let function_call = if is_async { quote! { #name ( #(#arg_tuple),* ) .await } @@ -256,7 +257,8 @@ fn dfn_macro( }; Ok(quote! { - #[export_name = #export_name] + #[cfg_attr(target_family = "wasm", export_name = #export_name)] + #[cfg_attr(not(target_family = "wasm"), export_name = #host_compatible_name)] fn #outer_function_ident() { ic_cdk::setup(); @@ -327,7 +329,8 @@ mod test { }; let expected = quote! { - #[export_name = "canister_query query"] + #[cfg_attr(target_family = "wasm", export_name = "canister_query query")] + #[cfg_attr(not(target_family = "wasm"), export_name = "canister_query.query")] fn #fn_name() { ic_cdk::setup(); ic_cdk::spawn(async { @@ -369,7 +372,8 @@ mod test { }; let expected = quote! { - #[export_name = "canister_query query"] + #[cfg_attr(target_family = "wasm", export_name = "canister_query query")] + #[cfg_attr(not(target_family = "wasm"), export_name = "canister_query.query")] fn #fn_name() { ic_cdk::setup(); ic_cdk::spawn(async { @@ -412,7 +416,8 @@ mod test { }; let expected = quote! { - #[export_name = "canister_query query"] + #[cfg_attr(target_family = "wasm", export_name = "canister_query query")] + #[cfg_attr(not(target_family = "wasm"), export_name = "canister_query.query")] fn #fn_name() { ic_cdk::setup(); ic_cdk::spawn(async { @@ -455,7 +460,8 @@ mod test { }; let expected = quote! { - #[export_name = "canister_query query"] + #[cfg_attr(target_family = "wasm", export_name = "canister_query query")] + #[cfg_attr(not(target_family = "wasm"), export_name = "canister_query.query")] fn #fn_name() { ic_cdk::setup(); ic_cdk::spawn(async { @@ -498,7 +504,8 @@ mod test { }; let expected = quote! { - #[export_name = "canister_query query"] + #[cfg_attr(target_family = "wasm", export_name = "canister_query query")] + #[cfg_attr(not(target_family = "wasm"), export_name = "canister_query.query")] fn #fn_name() { ic_cdk::setup(); ic_cdk::spawn(async { @@ -541,7 +548,8 @@ mod test { }; let expected = quote! { - #[export_name = "canister_query query"] + #[cfg_attr(target_family = "wasm", export_name = "canister_query query")] + #[cfg_attr(not(target_family = "wasm"), export_name = "canister_query.query")] fn #fn_name() { ic_cdk::setup(); ic_cdk::spawn(async { @@ -584,7 +592,8 @@ mod test { }; let expected = quote! { - #[export_name = "canister_query custom_query"] + #[cfg_attr(target_family = "wasm", export_name = "canister_query custom_query")] + #[cfg_attr(not(target_family = "wasm"), export_name = "canister_query.custom_query")] fn #fn_name() { ic_cdk::setup(); ic_cdk::spawn(async { diff --git a/src/ic-cdk-timers/CHANGELOG.md b/src/ic-cdk-timers/CHANGELOG.md index 76daa57d0..743d4fd47 100644 --- a/src/ic-cdk-timers/CHANGELOG.md +++ b/src/ic-cdk-timers/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.9.0] - 2024-07-01 + +### Changed + +- Upgrade `ic-cdk` to v0.15. + ## [0.8.0] - 2024-05-17 ### Changed diff --git a/src/ic-cdk-timers/Cargo.toml b/src/ic-cdk-timers/Cargo.toml index 02262bf77..1e0b95336 100644 --- a/src/ic-cdk-timers/Cargo.toml +++ b/src/ic-cdk-timers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic-cdk-timers" -version = "0.8.0" +version = "0.9.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/src/ic-cdk-timers/README.md b/src/ic-cdk-timers/README.md index bf0cdb021..4539793ef 100644 --- a/src/ic-cdk-timers/README.md +++ b/src/ic-cdk-timers/README.md @@ -16,7 +16,7 @@ In `Cargo.toml`: ```toml [dependencies] -ic-cdk-timers = "0.4.0" +ic-cdk-timers = "0.9.0" ``` To schedule a one-shot task to be executed 1s later: diff --git a/src/ic-cdk/CHANGELOG.md b/src/ic-cdk/CHANGELOG.md index e4fc05c7f..21850b17d 100644 --- a/src/ic-cdk/CHANGELOG.md +++ b/src/ic-cdk/CHANGELOG.md @@ -6,19 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.15.0] - 2024-07-01 + +### Changed + +- BREAKING: Stable Memory always use 64-bit addresses and `stable64_*` system API. (#498) +- BREAKING: Add `log_visibility` to the management canister API types: (#497) + - `CanisterSettings` + - `DefiniteCanisterSettings`. + ## [0.14.0] - 2024-05-17 ## [0.13.3] - 2024-05-10 (yanked) ### Added -- Add `wasm_memory_limit` to the management canister API types: (#483) - * `CanisterSettings` - * `DefiniteCanisterSettings`. - Provide safe wrapper of `in_replicated_execution` in ic-cdk. (#489) ### Changed - Upgrade `ic0` to v0.23.0. (#489) +- BREAKING: Add `wasm_memory_limit` to the management canister API types: (#483) + - `CanisterSettings` + - `DefiniteCanisterSettings`. ## [0.13.2] - 2024-04-08 diff --git a/src/ic-cdk/Cargo.toml b/src/ic-cdk/Cargo.toml index 0ed5cbf69..bbda394fd 100644 --- a/src/ic-cdk/Cargo.toml +++ b/src/ic-cdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic-cdk" -version = "0.14.0" # sync with ic-cdk-macros +version = "0.15.0" # sync with ic-cdk-macros authors.workspace = true edition.workspace = true license.workspace = true @@ -27,7 +27,7 @@ ic0.workspace = true # Dependents won't accidentaly upgrading ic-cdk-macros only but not ic-cdk. # ic-cdk-macros is a hidden dependency, re-exported by ic-cdk. # It should not be included by users direcly. -ic-cdk-macros = { path = "../ic-cdk-macros", version = "=0.14.0" } +ic-cdk-macros = { path = "../ic-cdk-macros", version = "=0.15.0" } serde.workspace = true serde_bytes.workspace = true slotmap = { workspace = true, optional = true } diff --git a/src/ic-cdk/README.md b/src/ic-cdk/README.md index 4608337d6..5fa3244e1 100644 --- a/src/ic-cdk/README.md +++ b/src/ic-cdk/README.md @@ -25,7 +25,7 @@ In Cargo.toml: crate-type = ["cdylib"] [dependencies] -ic-cdk = "0.12" +ic-cdk = "0.15" # Only necessary if you want to define Candid data types candid = "0.10" ``` diff --git a/src/ic-cdk/src/api/management_canister/main/types.rs b/src/ic-cdk/src/api/management_canister/main/types.rs index ac185fa01..97c284fd3 100644 --- a/src/ic-cdk/src/api/management_canister/main/types.rs +++ b/src/ic-cdk/src/api/management_canister/main/types.rs @@ -4,6 +4,20 @@ use serde::{Deserialize, Serialize}; /// Canister ID is Principal. pub type CanisterId = Principal; +/// todo +#[derive( + CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, +)] +pub enum LogVisibility { + #[default] + #[serde(rename = "controllers")] + /// Only controllers of the canister can access the logs. + Controllers, + #[serde(rename = "public")] + /// Everyone is allowed to access the canister's logs. + Public, +} + /// Canister settings. /// /// The settings are optional. If they are not explicitly set, the default values will be applied automatically. @@ -52,6 +66,10 @@ pub struct CanisterSettings { /// /// Default value: 5_000_000_000_000 (5 trillion cycles). pub reserved_cycles_limit: Option, + /// Defines who is allowed to read the canister's logs. + /// + /// Default value: Controllers + pub log_visibility: Option, /// Must be a number between 0 and 248-1 (i.e 256TB), inclusively. /// /// It indicates the upper limit on the WASM heap memory consumption of the canister. @@ -317,6 +335,8 @@ pub struct DefiniteCanisterSettings { pub freezing_threshold: Nat, /// Reserved cycles limit. pub reserved_cycles_limit: Nat, + /// Visibility of canister logs. + pub log_visibility: LogVisibility, /// The Wasm memory limit. pub wasm_memory_limit: Nat, } diff --git a/src/ic-cdk/src/api/stable/canister.rs b/src/ic-cdk/src/api/stable/canister.rs index 12186bf4c..248b1980c 100644 --- a/src/ic-cdk/src/api/stable/canister.rs +++ b/src/ic-cdk/src/api/stable/canister.rs @@ -7,27 +7,12 @@ use super::*; pub struct CanisterStableMemory {} impl StableMemory for CanisterStableMemory { - fn stable_size(&self) -> u32 { - // SAFETY: ic0.stable_size is always safe to call. - unsafe { ic0::stable_size() as u32 } - } - - fn stable64_size(&self) -> u64 { + fn stable_size(&self) -> u64 { // SAFETY: ic0.stable64_size is always safe to call. unsafe { ic0::stable64_size() as u64 } } - fn stable_grow(&self, new_pages: u32) -> Result { - // SAFETY: ic0.stable_grow is always safe to call. - unsafe { - match ic0::stable_grow(new_pages as i32) { - -1 => Err(StableMemoryError::OutOfMemory), - x => Ok(x as u32), - } - } - } - - fn stable64_grow(&self, new_pages: u64) -> Result { + fn stable_grow(&self, new_pages: u64) -> Result { // SAFETY: ic0.stable64_grow is always safe to call. unsafe { match ic0::stable64_grow(new_pages as i64) { @@ -37,28 +22,14 @@ impl StableMemory for CanisterStableMemory { } } - fn stable_write(&self, offset: u32, buf: &[u8]) { - // SAFETY: `buf`, being &[u8], is a readable sequence of bytes, and therefore valid to pass to ic0.stable_write. - unsafe { - ic0::stable_write(offset as i32, buf.as_ptr() as i32, buf.len() as i32); - } - } - - fn stable64_write(&self, offset: u64, buf: &[u8]) { + fn stable_write(&self, offset: u64, buf: &[u8]) { // SAFETY: `buf`, being &[u8], is a readable sequence of bytes, and therefore valid to pass to ic0.stable64_write. unsafe { ic0::stable64_write(offset as i64, buf.as_ptr() as i64, buf.len() as i64); } } - fn stable_read(&self, offset: u32, buf: &mut [u8]) { - // SAFETY: `buf`, being &mut [u8], is a writable sequence of bytes, and therefore valid to pass to ic0.stable_read. - unsafe { - ic0::stable_read(buf.as_ptr() as i32, offset as i32, buf.len() as i32); - } - } - - fn stable64_read(&self, offset: u64, buf: &mut [u8]) { + fn stable_read(&self, offset: u64, buf: &mut [u8]) { // SAFETY: `buf`, being &mut [u8], is a writable sequence of bytes, and therefore valid to pass to ic0.stable64_read. unsafe { ic0::stable64_read(buf.as_ptr() as i64, offset as i64, buf.len() as i64); diff --git a/src/ic-cdk/src/api/stable/mod.rs b/src/ic-cdk/src/api/stable/mod.rs index 46fe2e253..47ead7ccf 100644 --- a/src/ic-cdk/src/api/stable/mod.rs +++ b/src/ic-cdk/src/api/stable/mod.rs @@ -3,7 +3,6 @@ //! You can check the [Internet Computer Specification](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-stable-memory) //! for a in-depth explanation of stable memory. mod canister; -mod private; #[cfg(test)] mod tests; @@ -11,17 +10,14 @@ pub use canister::CanisterStableMemory; use std::{error, fmt, io}; /// WASM page size in bytes. -pub const WASM_PAGE_SIZE_IN_BYTES: usize = 64 * 1024; // 64KB +pub const WASM_PAGE_SIZE_IN_BYTES: u64 = 64 * 1024; // 64KB static CANISTER_STABLE_MEMORY: CanisterStableMemory = CanisterStableMemory {}; /// A trait defining the stable memory API which each canister running on the IC can make use of pub trait StableMemory { /// Gets current size of the stable memory (in WASM pages). - fn stable_size(&self) -> u32; - - /// Similar to `stable_size` but with support for 64-bit addressed memory. - fn stable64_size(&self) -> u64; + fn stable_size(&self) -> u64; /// Attempts to grow the stable memory by `new_pages` (added pages). /// @@ -29,37 +25,23 @@ pub trait StableMemory { /// size that was reserved. /// /// *Note*: Pages are 64KiB in WASM. - fn stable_grow(&self, new_pages: u32) -> Result; - - /// Similar to `stable_grow` but with support for 64-bit addressed memory. - fn stable64_grow(&self, new_pages: u64) -> Result; + fn stable_grow(&self, new_pages: u64) -> Result; /// Writes data to the stable memory location specified by an offset. /// /// Warning - this will panic if `offset + buf.len()` exceeds the current size of stable memory. /// Use `stable_grow` to request more stable memory if needed. - fn stable_write(&self, offset: u32, buf: &[u8]); - - /// Similar to `stable_write` but with support for 64-bit addressed memory. - fn stable64_write(&self, offset: u64, buf: &[u8]); + fn stable_write(&self, offset: u64, buf: &[u8]); /// Reads data from the stable memory location specified by an offset. - fn stable_read(&self, offset: u32, buf: &mut [u8]); - - /// Similar to `stable_read` but with support for 64-bit addressed memory. - fn stable64_read(&self, offset: u64, buf: &mut [u8]); + fn stable_read(&self, offset: u64, buf: &mut [u8]); } /// Gets current size of the stable memory (in WASM pages). -pub fn stable_size() -> u32 { +pub fn stable_size() -> u64 { CANISTER_STABLE_MEMORY.stable_size() } -/// Similar to `stable_size` but with support for 64-bit addressed memory. -pub fn stable64_size() -> u64 { - CANISTER_STABLE_MEMORY.stable64_size() -} - /// A possible error value when dealing with stable memory. #[derive(Debug)] pub enum StableMemoryError { @@ -86,49 +68,40 @@ impl error::Error for StableMemoryError {} /// size that was reserved. /// /// *Note*: Pages are 64KiB in WASM. -pub fn stable_grow(new_pages: u32) -> Result { +pub fn stable_grow(new_pages: u64) -> Result { CANISTER_STABLE_MEMORY.stable_grow(new_pages) } -/// Similar to `stable_grow` but with support for 64-bit addressed memory. -pub fn stable64_grow(new_pages: u64) -> Result { - CANISTER_STABLE_MEMORY.stable64_grow(new_pages) -} - /// Writes data to the stable memory location specified by an offset. /// /// Warning - this will panic if `offset + buf.len()` exceeds the current size of stable memory. /// Use `stable_grow` to request more stable memory if needed. -pub fn stable_write(offset: u32, buf: &[u8]) { +pub fn stable_write(offset: u64, buf: &[u8]) { CANISTER_STABLE_MEMORY.stable_write(offset, buf) } -/// Similar to `stable_write` but with support for 64-bit addressed memory. -pub fn stable64_write(offset: u64, buf: &[u8]) { - CANISTER_STABLE_MEMORY.stable64_write(offset, buf) -} - /// Reads data from the stable memory location specified by an offset. -pub fn stable_read(offset: u32, buf: &mut [u8]) { +pub fn stable_read(offset: u64, buf: &mut [u8]) { CANISTER_STABLE_MEMORY.stable_read(offset, buf) } -/// Similar to `stable_read` but with support for 64-bit addressed memory. -pub fn stable64_read(offset: u64, buf: &mut [u8]) { - CANISTER_STABLE_MEMORY.stable64_read(offset, buf) -} - /// Returns a copy of the stable memory. /// /// This will map the whole memory (even if not all of it has been written to). +/// +/// # Panics +/// +/// When the bytes of the stable memory cannot fit into a `Vec` which constrained by the usize. pub fn stable_bytes() -> Vec { - let size = (stable_size() as usize) << 16; + let size = (stable_size() << 16) + .try_into() + .expect("overflow: stable memory too large to read in one go"); let mut vec = Vec::with_capacity(size); // SAFETY: // `vec`, being mutable and allocated to `size` bytes, is safe to pass to ic0.stable_read with no offset. // ic0.stable_read writes to all of `vec[0..size]`, so `set_len` is safe to call with the new size. unsafe { - ic0::stable_read(vec.as_ptr() as i32, 0, size as i32); + ic0::stable64_read(vec.as_ptr() as i64, 0, size as i64); vec.set_len(size); } vec @@ -143,12 +116,12 @@ pub fn stable_bytes() -> Vec { /// Will attempt to grow the memory as it writes, /// and keep offsets and total capacity. #[derive(Debug)] -pub struct StableIO { +pub struct StableIO { /// The offset of the next write. - offset: A, + offset: u64, /// The capacity, in pages. - capacity: A, + capacity: u64, /// The stable memory to write data to. memory: M, @@ -164,127 +137,119 @@ impl Default for StableIO { // // We use a macro here since capturing all the traits required to add and manipulate memory // addresses with generics becomes cumbersome. -macro_rules! impl_stable_io { - ($address:ty) => { - impl + StableMemory> StableIO { - /// Creates a new `StableIO` which writes to the selected memory - pub fn with_memory(memory: M, offset: $address) -> Self { - let capacity = memory.stable_size_(); - - Self { - offset, - capacity, - memory, - } - } - /// Returns the offset of the writer - pub fn offset(&self) -> $address { - self.offset - } - - /// Attempts to grow the memory by adding new pages. - pub fn grow(&mut self, new_pages: $address) -> Result<(), StableMemoryError> { - let old_page_count = self.memory.stable_grow_(new_pages)?; - self.capacity = old_page_count + new_pages; - Ok(()) - } +impl StableIO { + /// Creates a new `StableIO` which writes to the selected memory + pub fn with_memory(memory: M, offset: u64) -> Self { + let capacity = memory.stable_size(); + Self { + offset, + capacity, + memory, + } + } - /// Writes a byte slice to the buffer. - /// - /// The only condition where this will - /// error out is if it cannot grow the memory. - pub fn write(&mut self, buf: &[u8]) -> Result { - let required_capacity_bytes = self.offset + buf.len() as $address; - let required_capacity_pages = - ((required_capacity_bytes + WASM_PAGE_SIZE_IN_BYTES as $address - 1) - / WASM_PAGE_SIZE_IN_BYTES as $address); - let current_pages = self.capacity; - let additional_pages_required = - required_capacity_pages.saturating_sub(current_pages); - - if additional_pages_required > 0 { - self.grow(additional_pages_required)?; - } - - self.memory.stable_write_(self.offset, buf); - self.offset += buf.len() as $address; - Ok(buf.len()) - } + /// Returns the offset of the writer + pub fn offset(&self) -> u64 { + self.offset + } - /// Reads data from the stable memory location specified by an offset. - /// - /// Note: - /// The stable memory size is cached on creation of the StableReader. - /// Therefore, in following scenario, it will get an `OutOfBounds` error: - /// 1. Create a StableReader - /// 2. Write some data to the stable memory which causes it grow - /// 3. call `read()` to read the newly written bytes - pub fn read(&mut self, buf: &mut [u8]) -> Result { - let capacity_bytes = self.capacity * WASM_PAGE_SIZE_IN_BYTES as $address; - let read_buf = if buf.len() as $address + self.offset > capacity_bytes { - if self.offset < capacity_bytes { - &mut buf[..(capacity_bytes - self.offset) as usize] - } else { - return Err(StableMemoryError::OutOfBounds); - } - } else { - buf - }; - self.memory.stable_read_(self.offset, read_buf); - self.offset += read_buf.len() as $address; - Ok(read_buf.len()) - } + /// Attempts to grow the memory by adding new pages. + pub fn grow(&mut self, new_pages: u64) -> Result<(), StableMemoryError> { + let old_page_count = self.memory.stable_grow(new_pages)?; + self.capacity = old_page_count + new_pages; + Ok(()) + } - // Helper used to implement io::Seek - fn seek(&mut self, offset: io::SeekFrom) -> io::Result { - self.offset = match offset { - io::SeekFrom::Start(offset) => offset as $address, - io::SeekFrom::End(offset) => { - ((self.capacity * WASM_PAGE_SIZE_IN_BYTES as $address) as i64 + offset) - as $address - } - io::SeekFrom::Current(offset) => (self.offset as i64 + offset) as $address, - }; - - Ok(self.offset as u64) - } + /// Writes a byte slice to the buffer. + /// + /// # Errors + /// + /// When it cannot grow the memory to accommodate the new data. + pub fn write(&mut self, buf: &[u8]) -> Result { + let required_capacity_bytes = self.offset + buf.len() as u64; + let required_capacity_pages = + (required_capacity_bytes + WASM_PAGE_SIZE_IN_BYTES - 1) / WASM_PAGE_SIZE_IN_BYTES; + let current_pages = self.capacity; + let additional_pages_required = required_capacity_pages.saturating_sub(current_pages); + + if additional_pages_required > 0 { + self.grow(additional_pages_required)?; } - impl + StableMemory> io::Write - for StableIO - { - fn write(&mut self, buf: &[u8]) -> Result { - self.write(buf) - .map_err(|e| io::Error::new(io::ErrorKind::OutOfMemory, e)) - } + self.memory.stable_write(self.offset, buf); + self.offset += buf.len() as u64; + Ok(buf.len()) + } - fn flush(&mut self) -> Result<(), io::Error> { - // Noop. - Ok(()) + /// Reads data from the stable memory location specified by an offset. + /// + /// # Errors + /// + /// The stable memory size is cached on creation of the StableReader. + /// Therefore, in following scenario, it will get an `OutOfBounds` error: + /// 1. Create a StableReader + /// 2. Write some data to the stable memory which causes it grow + /// 3. call `read()` to read the newly written bytes + pub fn read(&mut self, buf: &mut [u8]) -> Result { + let capacity_bytes = self.capacity * WASM_PAGE_SIZE_IN_BYTES; + let read_buf = if buf.len() as u64 + self.offset > capacity_bytes { + if self.offset < capacity_bytes { + // When usize=u32: + // (capacity_bytes - self.offset) < buf.len() <= u32::MAX == usize::MAX. + // So the cast below won't panic. + &mut buf[..(capacity_bytes - self.offset).try_into().unwrap()] + } else { + return Err(StableMemoryError::OutOfBounds); } - } + } else { + buf + }; + self.memory.stable_read(self.offset, read_buf); + self.offset += read_buf.len() as u64; + Ok(read_buf.len()) + } - impl + StableMemory> io::Read - for StableIO - { - fn read(&mut self, buf: &mut [u8]) -> Result { - Self::read(self, buf).or(Ok(0)) // Read defines EOF to be success + // Helper used to implement io::Seek + fn seek(&mut self, offset: io::SeekFrom) -> io::Result { + self.offset = match offset { + io::SeekFrom::Start(offset) => offset, + io::SeekFrom::End(offset) => { + ((self.capacity * WASM_PAGE_SIZE_IN_BYTES) as i64 + offset) as u64 } - } + io::SeekFrom::Current(offset) => (self.offset as i64 + offset) as u64, + }; - impl + StableMemory> io::Seek - for StableIO - { - fn seek(&mut self, offset: io::SeekFrom) -> io::Result { - self.seek(offset) - } - } - }; + Ok(self.offset) + } +} + +impl io::Write for StableIO { + fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf) + .map_err(|e| io::Error::new(io::ErrorKind::OutOfMemory, e)) + } + + fn flush(&mut self) -> Result<(), io::Error> { + // Noop. + Ok(()) + } +} + +impl io::Read for StableIO { + fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(self, buf).or(Ok(0)) // Read defines EOF to be success + } +} + +impl io::Seek for StableIO { + fn seek(&mut self, offset: io::SeekFrom) -> io::Result { + self.seek(offset) + } } -impl_stable_io!(u32); -impl_stable_io!(u64); +// impl_stable_io!(u32); +// impl_stable_io!(u64); /// A writer to the stable memory. /// @@ -294,7 +259,7 @@ impl_stable_io!(u64); /// Will attempt to grow the memory as it writes, /// and keep offsets and total capacity. #[derive(Debug)] -pub struct StableWriter(StableIO); +pub struct StableWriter(StableIO); #[allow(clippy::derivable_impls)] impl Default for StableWriter { @@ -307,19 +272,19 @@ impl Default for StableWriter { impl StableWriter { /// Creates a new `StableWriter` which writes to the selected memory #[inline] - pub fn with_memory(memory: M, offset: usize) -> Self { - Self(StableIO::::with_memory(memory, offset as u32)) + pub fn with_memory(memory: M, offset: u64) -> Self { + Self(StableIO::::with_memory(memory, offset)) } /// Returns the offset of the writer #[inline] - pub fn offset(&self) -> usize { - self.0.offset() as usize + pub fn offset(&self) -> u64 { + self.0.offset() } /// Attempts to grow the memory by adding new pages. #[inline] - pub fn grow(&mut self, new_pages: u32) -> Result<(), StableMemoryError> { + pub fn grow(&mut self, new_pages: u64) -> Result<(), StableMemoryError> { self.0.grow(new_pages) } @@ -387,7 +352,7 @@ impl BufferedStableWriter { } /// Returns the offset of the writer - pub fn offset(&self) -> usize { + pub fn offset(&self) -> u64 { self.inner.get_ref().offset() } } @@ -413,7 +378,7 @@ impl io::Seek for BufferedStableWriter { /// /// Keeps an offset and reads off stable memory consecutively. #[derive(Debug)] -pub struct StableReader(StableIO); +pub struct StableReader(StableIO); #[allow(clippy::derivable_impls)] impl Default for StableReader { @@ -425,14 +390,14 @@ impl Default for StableReader { impl StableReader { /// Creates a new `StableReader` which reads from the selected memory #[inline] - pub fn with_memory(memory: M, offset: usize) -> Self { - Self(StableIO::::with_memory(memory, offset as u32)) + pub fn with_memory(memory: M, offset: u64) -> Self { + Self(StableIO::::with_memory(memory, offset)) } /// Returns the offset of the reader #[inline] - pub fn offset(&self) -> usize { - self.0.offset() as usize + pub fn offset(&self) -> u64 { + self.0.offset() } /// Reads data from the stable memory location specified by an offset. @@ -491,7 +456,7 @@ impl BufferedStableReader { } /// Returns the offset of the reader - pub fn offset(&self) -> usize { + pub fn offset(&self) -> u64 { self.inner.get_ref().offset() } } diff --git a/src/ic-cdk/src/api/stable/private.rs b/src/ic-cdk/src/api/stable/private.rs deleted file mode 100644 index e5f967b4a..000000000 --- a/src/ic-cdk/src/api/stable/private.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Private module for traits that provide implementations for both u32 and u64 address space. - -use super::*; -pub trait AddressSize {} -impl AddressSize for u64 {} -impl AddressSize for u32 {} - -// An internal helper trait to have stable memory API specific to an address size -pub trait StableMemory_ { - fn stable_size_(&self) -> A; - fn stable_grow_(&self, new_pages: A) -> Result; - fn stable_write_(&self, offset: A, buf: &[u8]); - fn stable_read_(&self, offset: A, buf: &mut [u8]); -} - -// Blanket implementation for 32-bit addresses for any stable memory implementation -impl StableMemory_ for M { - fn stable_read_(&self, offset: u32, buf: &mut [u8]) { - StableMemory::stable_read(self, offset, buf) - } - - fn stable_grow_(&self, new_pages: u32) -> Result { - StableMemory::stable_grow(self, new_pages) - } - - fn stable_size_(&self) -> u32 { - StableMemory::stable_size(self) - } - - fn stable_write_(&self, offset: u32, buf: &[u8]) { - StableMemory::stable_write(self, offset, buf) - } -} - -// Blanket implementation for 64-bit addresses for any stable memory implementation -impl StableMemory_ for M { - fn stable_read_(&self, offset: u64, buf: &mut [u8]) { - StableMemory::stable64_read(self, offset, buf) - } - - fn stable_grow_(&self, new_pages: u64) -> Result { - StableMemory::stable64_grow(self, new_pages) - } - - fn stable_size_(&self) -> u64 { - StableMemory::stable64_size(self) - } - - fn stable_write_(&self, offset: u64, buf: &[u8]) { - StableMemory::stable64_write(self, offset, buf) - } -} diff --git a/src/ic-cdk/src/api/stable/tests.rs b/src/ic-cdk/src/api/stable/tests.rs index 5141238e4..aaf592849 100644 --- a/src/ic-cdk/src/api/stable/tests.rs +++ b/src/ic-cdk/src/api/stable/tests.rs @@ -13,7 +13,10 @@ impl TestStableMemory { if bytes_len > 0 { let pages_required = pages_required(bytes_len); let bytes_required = pages_required * WASM_PAGE_SIZE_IN_BYTES; - memory.lock().unwrap().resize(bytes_required, 0); + memory + .lock() + .unwrap() + .resize(bytes_required.try_into().unwrap(), 0); } TestStableMemory { memory } @@ -21,30 +24,22 @@ impl TestStableMemory { } impl StableMemory for TestStableMemory { - fn stable_size(&self) -> u32 { + fn stable_size(&self) -> u64 { let bytes_len = self.memory.lock().unwrap().len(); - pages_required(bytes_len) as u32 + pages_required(bytes_len) } - fn stable64_size(&self) -> u64 { - self.stable_size() as u64 - } - - fn stable_grow(&self, new_pages: u32) -> Result { - let new_bytes = new_pages as usize * WASM_PAGE_SIZE_IN_BYTES; + fn stable_grow(&self, new_pages: u64) -> Result { + let new_bytes = new_pages * WASM_PAGE_SIZE_IN_BYTES; let mut vec = self.memory.lock().unwrap(); - let previous_len = vec.len(); - let new_len = vec.len() + new_bytes; - vec.resize(new_len, 0); - Ok((previous_len / WASM_PAGE_SIZE_IN_BYTES) as u32) - } - - fn stable64_grow(&self, new_pages: u64) -> Result { - self.stable_grow(new_pages as u32).map(|len| len as u64) + let previous_len = vec.len() as u64; + let new_len = vec.len() as u64 + new_bytes; + vec.resize(new_len.try_into().unwrap(), 0); + Ok(previous_len / WASM_PAGE_SIZE_IN_BYTES) } - fn stable_write(&self, offset: u32, buf: &[u8]) { + fn stable_write(&self, offset: u64, buf: &[u8]) { let offset = offset as usize; let mut vec = self.memory.lock().unwrap(); @@ -54,11 +49,7 @@ impl StableMemory for TestStableMemory { vec[offset..(offset + buf.len())].clone_from_slice(buf); } - fn stable64_write(&self, offset: u64, buf: &[u8]) { - self.stable_write(offset as u32, buf) - } - - fn stable_read(&self, offset: u32, buf: &mut [u8]) { + fn stable_read(&self, offset: u64, buf: &mut [u8]) { let offset = offset as usize; let vec = self.memory.lock().unwrap(); @@ -66,15 +57,11 @@ impl StableMemory for TestStableMemory { buf[..count_to_copy].copy_from_slice(&vec[offset..offset + count_to_copy]); } - - fn stable64_read(&self, offset: u64, buf: &mut [u8]) { - self.stable_read(offset as u32, buf) - } } -fn pages_required(bytes_len: usize) -> usize { +fn pages_required(bytes_len: usize) -> u64 { let page_size = WASM_PAGE_SIZE_IN_BYTES; - (bytes_len + page_size - 1) / page_size + (bytes_len as u64 + page_size - 1) / page_size } mod stable_writer_tests { @@ -146,11 +133,11 @@ mod stable_writer_tests { } writer.flush().unwrap(); - let capacity_pages = TestStableMemory::new(memory).stable64_size(); + let capacity_pages = TestStableMemory::new(memory).stable_size(); let min_pages_required = - (total_bytes + WASM_PAGE_SIZE_IN_BYTES - 1) / WASM_PAGE_SIZE_IN_BYTES; + (total_bytes as u64 + WASM_PAGE_SIZE_IN_BYTES - 1) / WASM_PAGE_SIZE_IN_BYTES; - assert_eq!(capacity_pages, min_pages_required as u64); + assert_eq!(capacity_pages, min_pages_required); } #[test] @@ -161,7 +148,7 @@ mod stable_writer_tests { let mut writer = StableWriter::with_memory(TestStableMemory::new(memory.clone()), 0); assert_eq!(writer.offset(), 0); assert_eq!(writer.write(&vec![0; WRITE_SIZE]).unwrap(), WRITE_SIZE); - assert_eq!(writer.offset(), WRITE_SIZE); + assert_eq!(writer.offset(), WRITE_SIZE as u64); let mut writer = BufferedStableWriter::with_writer( WRITE_SIZE - 1, @@ -169,7 +156,7 @@ mod stable_writer_tests { ); assert_eq!(writer.offset(), 0); assert_eq!(writer.write(&vec![0; WRITE_SIZE]).unwrap(), WRITE_SIZE); - assert_eq!(writer.offset(), WRITE_SIZE); + assert_eq!(writer.offset(), WRITE_SIZE as u64); } #[test] @@ -177,18 +164,15 @@ mod stable_writer_tests { let memory = Rc::new(Mutex::new(Vec::new())); let mut writer = StableWriter::with_memory(TestStableMemory::new(memory.clone()), 0); writer - .seek(std::io::SeekFrom::Start(WASM_PAGE_SIZE_IN_BYTES as u64)) + .seek(std::io::SeekFrom::Start(WASM_PAGE_SIZE_IN_BYTES)) .unwrap(); - assert_eq!( - writer.stream_position().unwrap() as usize, - WASM_PAGE_SIZE_IN_BYTES - ); + assert_eq!(writer.stream_position().unwrap(), WASM_PAGE_SIZE_IN_BYTES); assert_eq!(writer.write(&[1_u8]).unwrap(), 1); assert_eq!( - writer.seek(std::io::SeekFrom::End(0)).unwrap() as usize, + writer.seek(std::io::SeekFrom::End(0)).unwrap(), WASM_PAGE_SIZE_IN_BYTES * 2 ); - let capacity_pages = TestStableMemory::new(memory).stable64_size(); + let capacity_pages = TestStableMemory::new(memory).stable_size(); assert_eq!(capacity_pages, 2); } @@ -233,7 +217,7 @@ mod stable_reader_tests { assert_eq!(reader.offset(), 0); let mut bytes = vec![0; READ_SIZE]; assert_eq!(reader.read(&mut bytes).unwrap(), READ_SIZE); - assert_eq!(reader.offset(), READ_SIZE); + assert_eq!(reader.offset(), READ_SIZE as u64); let mut reader = BufferedStableReader::with_reader( READ_SIZE - 1, @@ -242,7 +226,7 @@ mod stable_reader_tests { assert_eq!(reader.offset(), 0); let mut bytes = vec![0; READ_SIZE]; assert_eq!(reader.read(&mut bytes).unwrap(), READ_SIZE); - assert_eq!(reader.offset(), READ_SIZE); + assert_eq!(reader.offset(), READ_SIZE as u64); } #[test] @@ -260,11 +244,11 @@ mod stable_reader_tests { assert_eq!(reader.read(&mut bytes).unwrap(), 1); assert_eq!(&bytes, &[OFFSET as u8]); assert_eq!( - reader.seek(std::io::SeekFrom::End(0)).unwrap() as usize, + reader.seek(std::io::SeekFrom::End(0)).unwrap(), WASM_PAGE_SIZE_IN_BYTES ); reader - .seek(std::io::SeekFrom::Start(WASM_PAGE_SIZE_IN_BYTES as u64 * 2)) + .seek(std::io::SeekFrom::Start(WASM_PAGE_SIZE_IN_BYTES * 2)) .unwrap(); // out of bounds so should fail assert!(reader.read(&mut bytes).is_err());