diff --git a/packages/pocket-ic/BUILD.bazel b/packages/pocket-ic/BUILD.bazel index 57e445d9270..c300ced383d 100644 --- a/packages/pocket-ic/BUILD.bazel +++ b/packages/pocket-ic/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test", "rust_test_suite") +load("@rules_rust//rust:defs.bzl", "rust_doc_test", "rust_library", "rust_test", "rust_test_suite") package(default_visibility = ["//visibility:public"]) @@ -51,6 +51,11 @@ rust_library( deps = DEPENDENCIES, ) +rust_doc_test( + name = "pocket-ic-doc-test", + crate = ":pocket-ic", +) + rust_test( name = "pocket-ic-test", srcs = glob(["src/**/*.rs"]), diff --git a/packages/pocket-ic/CHANGELOG.md b/packages/pocket-ic/CHANGELOG.md index 80f45471327..08ab5d8349e 100644 --- a/packages/pocket-ic/CHANGELOG.md +++ b/packages/pocket-ic/CHANGELOG.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The function `PocketIc::await_call_no_ticks` to await the status of an update call (submitted through an ingress message) becoming known without triggering round execution (round execution must be triggered separarely, e.g., on a "live" instance or by separate PocketIC library calls). +### Changed +- The response types `pocket_ic::WasmResult`, `pocket_ic::UserError`, and `pocket_ic::CallError` are replaced by a single reject response type `pocket_ic::RejectResponse`. + ## 6.0.0 - 2024-11-13 diff --git a/packages/pocket-ic/HOWTO.md b/packages/pocket-ic/HOWTO.md index e1244a3cfcf..8c9a10a8ea0 100644 --- a/packages/pocket-ic/HOWTO.md +++ b/packages/pocket-ic/HOWTO.md @@ -242,23 +242,22 @@ Here is a sketch of a test for a canister making canister HTTP outcalls: fn test_canister_http() { let pic = PocketIc::new(); - // Create a canister and charge it with 100T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, 100_000_000_000_000); + // Create a canister and charge it with 2T cycles. + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, 2_000_000_000_000); // Install the test canister wasm file on the canister. - let test_wasm = [...]; - pic.install_canister(can_id, test_wasm, vec![], None); + let test_wasm = todo!(); + pic.install_canister(canister_id, test_wasm, vec![], None); // Submit an update call to the test canister making a canister http outcall // and mock a canister http outcall response. - let arg_bytes = Encode!(&()).unwrap(); let call_id = pic .submit_call( - can_id, + canister_id, Principal::anonymous(), "canister_http", - arg_bytes, + encode_one(()).unwrap(), ) .unwrap(); @@ -283,22 +282,16 @@ fn test_canister_http() { }; pic.mock_canister_http_response(mock_canister_http_response); - // Now the test canister will receive the http outcall response - // and reply to the ingress message from the test driver - // relaying the received http outcall response. - let reply = pic.await_call(call_id).unwrap(); - match reply { - WasmResult::Reply(data) => { - let http_response: Result = - decode_one(&data).unwrap(); - assert_eq!(http_response.unwrap().body, body); - } - WasmResult::Reject(msg) => panic!("Unexpected reject {}", msg), - }; - // There should be no more pending canister http outcalls. let canister_http_requests = pic.get_canister_http(); assert_eq!(canister_http_requests.len(), 0); + + // Now the test canister will receive the http outcall response + // and reply to the ingress message from the test driver. + let reply = pic.await_call(call_id).unwrap(); + let http_response: Result = + decode_one(&reply).unwrap(); + assert_eq!(http_response.unwrap().body, body); } ``` @@ -332,18 +325,12 @@ e.g., 13 for a regular application subnet. // and reply to the ingress message from the test driver // relaying the error. let reply = pic.await_call(call_id).unwrap(); - match reply { - WasmResult::Reply(data) => { - let http_response: Result = - decode_one(&data).unwrap(); - let (reject_code, err) = http_response.unwrap_err(); - assert_eq!(reject_code, RejectionCode::SysTransient); - assert!( - err.contains("No consensus could be reached. Replicas had different responses.") - ); - } - WasmResult::Reject(msg) => panic!("Unexpected reject {}", msg), - }; + let http_response: Result = + decode_one(&reply).unwrap(); + let (reject_code, err) = http_response.unwrap_err(); + assert!(matches!(reject_code, RejectionCode::SysTransient)); + let expected = "No consensus could be reached. Replicas had different responses. Details: request_id: 0, timeout: 1620328930000000005, hashes: [98387cc077af9cff2ef439132854e91cb074035bb76e2afb266960d8e3beaf11: 2], [6a2fa8e54fb4bbe62cde29f7531223d9fcf52c21c03500c1060a5f893ed32d2e: 2], [3e9ec98abf56ef680bebb14309858ede38f6fde771cd4c04cda8f066dc2810db: 2], [2c14e77f18cd990676ae6ce0d7eb89c0af9e1a66e17294b5f0efa68422bba4cb: 2], [2843e4133f673571ff919808d3ca542cc54aaf288c702944e291f0e4fafffc69: 2], [1c4ad84926c36f1fbc634a0dc0535709706f7c48f0c6ebd814fe514022b90671: 2], [7bf80e2f02011ab0a7836b526546e75203b94e856d767c9df4cb0c19baf34059: 1]"; + assert_eq!(err, expected); ``` In the live mode (see the section "Live Mode" for more details), the canister HTTP outcalls are processed diff --git a/packages/pocket-ic/README.md b/packages/pocket-ic/README.md index 3bc229280c3..e09ef9cc0d2 100644 --- a/packages/pocket-ic/README.md +++ b/packages/pocket-ic/README.md @@ -7,28 +7,43 @@ With PocketIC Rust, testing canisters is as simple as calling Rust functions. Here is a simple example: ```rust -use candid::encode_one; +use candid::{Principal, encode_one}; use pocket_ic::PocketIc; - #[test] - fn test_counter_canister() { +// 2T cycles +const INIT_CYCLES: u128 = 2_000_000_000_000; + +#[test] +fn test_counter_canister() { let pic = PocketIc::new(); - // Create an empty canister as the anonymous principal and add cycles. + + // Create a canister and charge it with 2T cycles. let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, 2_000_000_000_000); - - let wasm_bytes = load_counter_wasm(...); - pic.install_canister(canister_id, wasm_bytes, vec![], None); - // 'inc' is a counter canister method. - call_counter_canister(&pic, canister_id, "inc"); - // Check if it had the desired effect. - let reply = call_counter_canister(&pic, canister_id, "read"); - assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 1])); - } - -fn call_counter_canister(pic: &PocketIc, canister_id: CanisterId, method: &str) -> WasmResult { - pic.update_call(canister_id, Principal::anonymous(), method, encode_one(()).unwrap()) - .expect("Failed to call counter canister") + pic.add_cycles(canister_id, INIT_CYCLES); + + // Install the counter canister wasm file on the canister. + let counter_wasm = todo!(); + pic.install_canister(canister_id, counter_wasm, vec![], None); + + // Make some calls to the canister. + let reply = call_counter_can(&pic, canister_id, "read"); + assert_eq!(reply, vec![0, 0, 0, 0]); + let reply = call_counter_can(&pic, canister_id, "write"); + assert_eq!(reply, vec![1, 0, 0, 0]); + let reply = call_counter_can(&pic, canister_id, "write"); + assert_eq!(reply, vec![2, 0, 0, 0]); + let reply = call_counter_can(&pic, canister_id, "read"); + assert_eq!(reply, vec![2, 0, 0, 0]); +} + +fn call_counter_can(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec { + pic.update_call( + canister_id, + Principal::anonymous(), + method, + encode_one(()).unwrap(), + ) + .expect("Failed to call counter canister") } ``` diff --git a/packages/pocket-ic/src/common/rest.rs b/packages/pocket-ic/src/common/rest.rs index a99c2241db5..0413b7bb5ba 100644 --- a/packages/pocket-ic/src/common/rest.rs +++ b/packages/pocket-ic/src/common/rest.rs @@ -2,7 +2,7 @@ //! The types in this module are used to serialize and deserialize data //! from and to JSON, and are used by both crates. -use crate::UserError; +use crate::RejectResponse; use candid::Principal; use hex; use reqwest::Response; @@ -128,12 +128,6 @@ pub struct RawIngressStatusArgs { pub raw_caller: Option, } -#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] -pub enum RawSubmitIngressResult { - Ok(RawMessageId), - Err(UserError), -} - #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] pub struct RawCanisterCall { #[serde(deserialize_with = "base64::deserialize")] @@ -151,21 +145,30 @@ pub struct RawCanisterCall { #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] pub enum RawCanisterResult { - Ok(RawWasmResult), - Err(UserError), -} - -#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] -pub enum RawWasmResult { - /// Raw response, returned in a "happy" case - Reply( + Ok( #[serde(deserialize_with = "base64::deserialize")] #[serde(serialize_with = "base64::serialize")] Vec, ), - /// Returned with an error message when the canister decides to reject the - /// message - Reject(String), + Err(RejectResponse), +} + +impl From, RejectResponse>> for RawCanisterResult { + fn from(result: Result, RejectResponse>) -> Self { + match result { + Ok(data) => RawCanisterResult::Ok(data), + Err(reject_response) => RawCanisterResult::Err(reject_response), + } + } +} + +impl From for Result, RejectResponse> { + fn from(result: RawCanisterResult) -> Self { + match result { + RawCanisterResult::Ok(data) => Ok(data), + RawCanisterResult::Err(reject_response) => Err(reject_response), + } + } } #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] diff --git a/packages/pocket-ic/src/lib.rs b/packages/pocket-ic/src/lib.rs index 393be384e36..cf74dbed171 100644 --- a/packages/pocket-ic/src/lib.rs +++ b/packages/pocket-ic/src/lib.rs @@ -1,39 +1,57 @@ #![allow(clippy::test_attr_in_doctest)] -//! # PocketIC: A Canister Testing Platform -//! -//! PocketIC is the local canister smart contract testing platform for the [Internet Computer](https://internetcomputer.org/). -//! -//! It consists of the PocketIC server, which can run many independent IC instances, and a client library (this crate), which provides an interface to your IC instances. -//! -//! With PocketIC, testing canisters is as simple as calling rust functions. Here is a minimal example: -//! -//! ```rust -//! use candid::{Principal, encode_one}; -//! use pocket_ic::{WasmResult, PocketIc}; -//! -//! #[test] -//! fn test_counter_canister() { -//! let pic = PocketIc::new(); -//! // Create an empty canister as the anonymous principal and add cycles. -//! let canister_id = pic.create_canister(); -//! pic.add_cycles(canister_id, 2_000_000_000_000); -//! -//! let wasm_bytes = todo!(); -//! pic.install_canister(canister_id, wasm_bytes, vec![], None); -//! // 'inc' is a counter canister method. -//! call_counter_canister(&pic, canister_id, "inc"); -//! // Check if it had the desired effect. -//! let reply = call_counter_canister(&pic, canister_id, "read"); -//! assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 1])); -//! } -//! -//! fn call_counter_canister(pic: &PocketIc, canister_id: Principal, method: &str) -> WasmResult { -//! pic.update_call(canister_id, Principal::anonymous(), method, encode_one(()).unwrap()) -//! .expect("Failed to call counter canister") -//! } -//! ``` -//! For more information, see the [README](https://crates.io/crates/pocket-ic). -//! +/// # PocketIC: A Canister Testing Platform +/// +/// PocketIC is the local canister smart contract testing platform for the [Internet Computer](https://internetcomputer.org/). +/// +/// It consists of the PocketIC server, which can run many independent IC instances, and a client library (this crate), which provides an interface to your IC instances. +/// +/// With PocketIC, testing canisters is as simple as calling rust functions. Here is a minimal example: +/// +/// ```rust +/// use candid::{Principal, encode_one}; +/// use pocket_ic::PocketIc; +/// +/// // 2T cycles +/// const INIT_CYCLES: u128 = 2_000_000_000_000; +/// +/// // Create a counter canister and charge it with 2T cycles. +/// fn deploy_counter_canister(pic: &PocketIc) -> Principal { +/// let canister_id = pic.create_canister(); +/// pic.add_cycles(canister_id, INIT_CYCLES); +/// let counter_wasm = todo!(); +/// pic.install_canister(canister_id, counter_wasm, vec![], None); +/// canister_id +/// } +/// +/// // Call a method on the counter canister as the anonymous principal. +/// fn call_counter_canister(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec { +/// pic.update_call( +/// canister_id, +/// Principal::anonymous(), +/// method, +/// encode_one(()).unwrap(), +/// ) +/// .expect("Failed to call counter canister") +/// } +/// +/// #[test] +/// fn test_counter_canister() { +/// let pic = PocketIc::new(); +/// let canister_id = deploy_counter_canister(&pic); +/// +/// // Make some calls to the counter canister. +/// let reply = call_counter_canister(&pic, canister_id, "read"); +/// assert_eq!(reply, vec![0, 0, 0, 0]); +/// let reply = call_counter_canister(&pic, canister_id, "write"); +/// assert_eq!(reply, vec![1, 0, 0, 0]); +/// let reply = call_counter_canister(&pic, canister_id, "write"); +/// assert_eq!(reply, vec![2, 0, 0, 0]); +/// let reply = call_counter_canister(&pic, canister_id, "read"); +/// assert_eq!(reply, vec![2, 0, 0, 0]); +/// } +/// ``` +/// For more information, see the [README](https://crates.io/crates/pocket-ic). +/// use crate::{ common::rest::{ BlobCompression, BlobId, CanisterHttpRequest, ExtendedSubnetConfigSet, HttpsConfig, @@ -637,7 +655,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -654,7 +672,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -670,7 +688,7 @@ impl PocketIc { } /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`. - pub fn await_call(&self, message_id: RawMessageId) -> Result { + pub fn await_call(&self, message_id: RawMessageId) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.await_call(message_id).await }) } @@ -690,7 +708,7 @@ impl PocketIc { /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`. /// This function does not execute rounds and thus should only be called on a "live" PocketIC instance /// or if rounds are executed due to separate PocketIC library calls. - pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result { + pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await }) } @@ -703,7 +721,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -720,7 +738,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -734,7 +752,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Principal, - ) -> Result, CallError> { + ) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -749,7 +767,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result { + ) -> Result { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await }) } @@ -822,7 +840,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, chunk: Vec, - ) -> Result, CallError> { + ) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -837,7 +855,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result>, CallError> { + ) -> Result>, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await }) } @@ -848,7 +866,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await }) } @@ -864,7 +882,7 @@ impl PocketIc { chunk_hashes_list: Vec>, wasm_module_hash: Vec, arg: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -906,7 +924,7 @@ impl PocketIc { wasm_module: Vec, arg: Vec, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -923,7 +941,7 @@ impl PocketIc { wasm_module: Vec, arg: Vec, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -938,7 +956,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await }) } @@ -950,7 +968,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, replace_snapshot: Option>, - ) -> Result { + ) -> Result { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -966,7 +984,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, snapshot_id: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -981,7 +999,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result, CallError> { + ) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -997,7 +1015,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, snapshot_id: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -1013,7 +1031,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, settings: CanisterSettings, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -1029,7 +1047,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, new_controllers: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -1044,7 +1062,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await }) } @@ -1055,7 +1073,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await }) } @@ -1066,7 +1084,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await }) } @@ -1099,7 +1117,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -1125,7 +1143,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { let runtime = self.runtime.clone(); runtime.block_on(async { self.pocket_ic @@ -1199,7 +1217,7 @@ pub fn call_candid_as( sender: Principal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1223,7 +1241,7 @@ pub fn call_candid( effective_principal: RawEffectivePrincipal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1244,7 +1262,7 @@ pub fn query_candid( canister_id: CanisterId, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1260,7 +1278,7 @@ pub fn query_candid_as( sender: Principal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1276,7 +1294,7 @@ pub fn update_candid( canister_id: CanisterId, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1292,7 +1310,7 @@ pub fn update_candid_as( sender: Principal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1306,15 +1324,15 @@ where /// [`query_candid`]. pub fn with_candid( input: Input, - f: impl FnOnce(Vec) -> Result, -) -> Result + f: impl FnOnce(Vec) -> Result, RejectResponse>, +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, { let in_bytes = encode_args(input).expect("failed to encode args"); - match f(in_bytes) { - Ok(WasmResult::Reply(out_bytes)) => Ok(decode_args(&out_bytes).unwrap_or_else(|e| { + f(in_bytes).map(|out_bytes| { + decode_args(&out_bytes).unwrap_or_else(|e| { panic!( "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}", std::any::type_name::(), @@ -1322,10 +1340,8 @@ where out_bytes, String::from_utf8_lossy(&out_bytes), ) - })), - Ok(WasmResult::Reject(message)) => Err(CallError::Reject(message)), - Err(user_error) => Err(CallError::UserError(user_error)), - } + }) + }) } /// Error type for [`TryFrom`]. @@ -1499,31 +1515,62 @@ impl std::fmt::Display for ErrorCode { } } -/// The error that is sent back to users from the IC if something goes -/// wrong. It's designed to be copyable and serializable so that we -/// can persist it in the ingress history. +/// User-facing reject codes. +/// +/// They can be derived from the most significant digit of the +/// corresponding error code. #[derive( - PartialOrd, Ord, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, + PartialOrd, + Ord, + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + JsonSchema, + EnumIter, )] -pub struct UserError { - /// The error code. - pub code: ErrorCode, - /// A human-readable description of the error. - pub description: String, +pub enum RejectCode { + SysFatal = 1, + SysTransient = 2, + DestinationInvalid = 3, + CanisterReject = 4, + CanisterError = 5, + SysUnknown = 6, } -impl std::fmt::Display for UserError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // E.g. "IC0301: Canister 42 not found" - write!(f, "{}: {}", self.code, self.description) +impl TryFrom for RejectCode { + type Error = TryFromError; + fn try_from(err: u64) -> Result { + match err { + 1 => Ok(RejectCode::SysFatal), + 2 => Ok(RejectCode::SysTransient), + 3 => Ok(RejectCode::DestinationInvalid), + 4 => Ok(RejectCode::CanisterReject), + 5 => Ok(RejectCode::CanisterError), + 6 => Ok(RejectCode::SysUnknown), + _ => Err(TryFromError::ValueOutOfRange(err)), + } } } -/// This enum describes the different error types when invoking a canister. -#[derive(Debug, Serialize, Deserialize)] -pub enum CallError { - Reject(String), - UserError(UserError), +/// User-facing type describing an unsuccessful (also called reject) call response. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +pub struct RejectResponse { + pub reject_code: RejectCode, + pub reject_message: String, + pub error_code: ErrorCode, + pub certified: bool, +} + +impl std::fmt::Display for RejectResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Follows [agent-rs](https://github.com/dfinity/agent-rs/blob/a651dbbe69e61d4e8508c144cd60cfa3118eeb3a/ic-agent/src/agent/agent_error.rs#L54) + write!(f, "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}", self.reject_code, self.reject_message, self.error_code) + } } /// This enum describes the result of retrieving ingress status. @@ -1535,18 +1582,7 @@ pub enum CallError { pub enum IngressStatusResult { NotAvailable, Forbidden(String), - Success(Result), -} - -/// This struct describes the different types that executing a WASM function in -/// a canister can produce. -#[derive(PartialOrd, Ord, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum WasmResult { - /// Raw response, returned in a successful case. - Reply(#[serde(with = "serde_bytes")] Vec), - /// Returned with an error message when the canister decides to reject the - /// message. - Reject(String), + Success(Result, RejectResponse>), } #[cfg(windows)] @@ -1691,9 +1727,18 @@ pub fn get_default_effective_canister_id( #[cfg(test)] mod test { - use crate::ErrorCode; + use crate::{ErrorCode, RejectCode}; use strum::IntoEnumIterator; + #[test] + fn reject_code_round_trip() { + for initial in RejectCode::iter() { + let round_trip = RejectCode::try_from(initial as u64).unwrap(); + + assert_eq!(initial, round_trip); + } + } + #[test] fn error_code_round_trip() { for initial in ErrorCode::iter() { @@ -1703,6 +1748,21 @@ mod test { } } + #[test] + fn reject_code_matches_ic_error_code() { + assert_eq!( + RejectCode::iter().len(), + ic_error_types::RejectCode::iter().len() + ); + for ic_reject_code in ic_error_types::RejectCode::iter() { + let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap(); + assert_eq!( + format!("{:?}", reject_code), + format!("{:?}", ic_reject_code) + ); + } + } + #[test] fn error_code_matches_ic_error_code() { assert_eq!( diff --git a/packages/pocket-ic/src/nonblocking.rs b/packages/pocket-ic/src/nonblocking.rs index 80d119bdf46..375b9034d9d 100644 --- a/packages/pocket-ic/src/nonblocking.rs +++ b/packages/pocket-ic/src/nonblocking.rs @@ -4,9 +4,8 @@ use crate::common::rest::{ HttpGatewayConfig, HttpGatewayInfo, HttpsConfig, InstanceConfig, InstanceId, MockCanisterHttpResponse, RawAddCycles, RawCanisterCall, RawCanisterHttpRequest, RawCanisterId, RawCanisterResult, RawCycles, RawEffectivePrincipal, RawIngressStatusArgs, RawMessageId, - RawMockCanisterHttpResponse, RawPrincipalId, RawSetStableMemory, RawStableMemory, - RawSubmitIngressResult, RawSubnetId, RawTime, RawVerifyCanisterSigArg, RawWasmResult, SubnetId, - Topology, + RawMockCanisterHttpResponse, RawPrincipalId, RawSetStableMemory, RawStableMemory, RawSubnetId, + RawTime, RawVerifyCanisterSigArg, SubnetId, Topology, }; use crate::management_canister::{ CanisterId, CanisterIdRecord, CanisterInstallMode, CanisterInstallModeUpgradeInner, @@ -17,7 +16,7 @@ use crate::management_canister::{ TakeCanisterSnapshotArgs, UpdateSettingsArgs, UploadChunkArgs, UploadChunkResult, }; pub use crate::DefaultEffectiveCanisterIdError; -use crate::{CallError, IngressStatusResult, PocketIcBuilder, UserError, WasmResult}; +use crate::{IngressStatusResult, PocketIcBuilder, RejectResponse}; use backoff::backoff::Backoff; use backoff::{ExponentialBackoff, ExponentialBackoffBuilder}; use candid::{ @@ -547,7 +546,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result { self.submit_call_with_effective_principal( canister_id, RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()), @@ -566,7 +565,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result { let endpoint = "update/submit_ingress_message"; let raw_canister_call = RawCanisterCall { sender: sender.as_slice().to_vec(), @@ -575,24 +574,14 @@ impl PocketIc { payload, effective_principal, }; - let res: RawSubmitIngressResult = self.post(endpoint, raw_canister_call).await; - match res { - RawSubmitIngressResult::Ok(message_id) => Ok(message_id), - RawSubmitIngressResult::Err(user_error) => Err(user_error), - } + self.post(endpoint, raw_canister_call).await } /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`. - pub async fn await_call(&self, message_id: RawMessageId) -> Result { + pub async fn await_call(&self, message_id: RawMessageId) -> Result, RejectResponse> { let endpoint = "update/await_ingress_message"; let result: RawCanisterResult = self.post(endpoint, message_id).await; - match result { - RawCanisterResult::Ok(raw_wasm_result) => match raw_wasm_result { - RawWasmResult::Reply(data) => Ok(WasmResult::Reply(data)), - RawWasmResult::Reject(text) => Ok(WasmResult::Reject(text)), - }, - RawCanisterResult::Err(user_error) => Err(user_error), - } + result.into() } /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`. @@ -608,18 +597,11 @@ impl PocketIc { raw_message_id, raw_caller: caller.map(|caller| caller.into()), }; - match self.try_post(endpoint, raw_ingress_status_args).await { + let result: Result, (StatusCode, String)> = + self.try_post(endpoint, raw_ingress_status_args).await; + match result { Ok(None) => IngressStatusResult::NotAvailable, - Ok(Some(raw_result)) => { - let result = match raw_result { - RawCanisterResult::Ok(raw_wasm_result) => match raw_wasm_result { - RawWasmResult::Reply(data) => Ok(WasmResult::Reply(data)), - RawWasmResult::Reject(text) => Ok(WasmResult::Reject(text)), - }, - RawCanisterResult::Err(user_error) => Err(user_error), - }; - IngressStatusResult::Success(result) - } + Ok(Some(result)) => IngressStatusResult::Success(result.into()), Err((status, message)) => { assert_eq!(status, StatusCode::FORBIDDEN, "HTTP error code {} for PocketIc::ingress_status is not StatusCode::FORBIDDEN. This is a bug!", status); IngressStatusResult::Forbidden(message) @@ -633,7 +615,7 @@ impl PocketIc { pub async fn await_call_no_ticks( &self, message_id: RawMessageId, - ) -> Result { + ) -> Result, RejectResponse> { let mut retry_policy: ExponentialBackoff = ExponentialBackoffBuilder::new() .with_initial_interval(Duration::from_millis(10)) .with_max_interval(Duration::from_secs(1)) @@ -657,7 +639,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { self.update_call_with_effective_principal( canister_id, RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()), @@ -676,7 +658,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { self.query_call_with_effective_principal( canister_id, RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()), @@ -698,7 +680,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { let endpoint = "read/query"; self.canister_call( endpoint, @@ -716,7 +698,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Principal, - ) -> Result, CallError> { + ) -> Result, RejectResponse> { with_candid::<_, (FetchCanisterLogsResult,), _>( (CanisterIdRecord { canister_id },), |payload| async { @@ -740,7 +722,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result { + ) -> Result { call_candid_as::<(CanisterIdRecord,), (CanisterStatusResult,)>( self, Principal::management_canister(), @@ -873,7 +855,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, chunk: Vec, - ) -> Result, CallError> { + ) -> Result, RejectResponse> { call_candid_as::<_, (UploadChunkResult,)>( self, Principal::management_canister(), @@ -892,7 +874,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result>, CallError> { + ) -> Result>, RejectResponse> { call_candid_as::<_, (StoredChunksResult,)>( self, Principal::management_canister(), @@ -911,7 +893,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as( self, Principal::management_canister(), @@ -934,7 +916,7 @@ impl PocketIc { chunk_hashes_list: Vec>, wasm_module_hash: Vec, arg: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as( self, Principal::management_canister(), @@ -964,7 +946,7 @@ impl PocketIc { wasm_module: Vec, arg: Vec, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { if wasm_module.len() + arg.len() < INSTALL_CHUNKED_CODE_THRESHOLD { call_candid_as::<(InstallCodeArgs,), ()>( self, @@ -1036,7 +1018,7 @@ impl PocketIc { wasm_module: Vec, arg: Vec, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { self.install_canister_helper( CanisterInstallMode::Upgrade(Some(CanisterInstallModeUpgradeInner { wasm_memory_persistence: Some( @@ -1060,7 +1042,7 @@ impl PocketIc { wasm_module: Vec, arg: Vec, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { self.install_canister_helper( CanisterInstallMode::Reinstall, canister_id, @@ -1077,7 +1059,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as::<(CanisterIdRecord,), ()>( self, Principal::management_canister(), @@ -1096,7 +1078,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, replace_snapshot: Option>, - ) -> Result { + ) -> Result { call_candid_as::<_, (Snapshot,)>( self, Principal::management_canister(), @@ -1119,7 +1101,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, snapshot_id: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as( self, Principal::management_canister(), @@ -1141,7 +1123,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result, CallError> { + ) -> Result, RejectResponse> { call_candid_as::<_, (Vec,)>( self, Principal::management_canister(), @@ -1161,7 +1143,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, snapshot_id: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as( self, Principal::management_canister(), @@ -1183,7 +1165,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, settings: CanisterSettings, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as::<_, ()>( self, Principal::management_canister(), @@ -1206,7 +1188,7 @@ impl PocketIc { canister_id: CanisterId, sender: Option, new_controllers: Vec, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { let settings = CanisterSettings { controllers: Some(new_controllers), ..CanisterSettings::default() @@ -1232,7 +1214,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as::<(CanisterIdRecord,), ()>( self, Principal::management_canister(), @@ -1250,7 +1232,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as::<(CanisterIdRecord,), ()>( self, Principal::management_canister(), @@ -1268,7 +1250,7 @@ impl PocketIc { &self, canister_id: CanisterId, sender: Option, - ) -> Result<(), CallError> { + ) -> Result<(), RejectResponse> { call_candid_as::<(CanisterIdRecord,), ()>( self, Principal::management_canister(), @@ -1492,7 +1474,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { let raw_canister_call = RawCanisterCall { sender: sender.as_slice().to_vec(), canister_id: canister_id.as_slice().to_vec(), @@ -1502,13 +1484,7 @@ impl PocketIc { }; let result: RawCanisterResult = self.post(endpoint, raw_canister_call).await; - match result { - RawCanisterResult::Ok(raw_wasm_result) => match raw_wasm_result { - RawWasmResult::Reply(data) => Ok(WasmResult::Reply(data)), - RawWasmResult::Reject(text) => Ok(WasmResult::Reject(text)), - }, - RawCanisterResult::Err(user_error) => Err(user_error), - } + result.into() } pub(crate) async fn update_call_with_effective_principal( @@ -1518,7 +1494,7 @@ impl PocketIc { sender: Principal, method: &str, payload: Vec, - ) -> Result { + ) -> Result, RejectResponse> { let message_id = self .submit_call_with_effective_principal( canister_id, @@ -1572,7 +1548,7 @@ pub async fn call_candid_as( sender: Principal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1598,7 +1574,7 @@ pub async fn call_candid( effective_principal: RawEffectivePrincipal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1620,7 +1596,7 @@ pub async fn query_candid( canister_id: CanisterId, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1636,7 +1612,7 @@ pub async fn query_candid_as( sender: Principal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1653,7 +1629,7 @@ pub async fn update_candid( canister_id: CanisterId, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1669,7 +1645,7 @@ pub async fn update_candid_as( sender: Principal, method: &str, input: Input, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, @@ -1685,15 +1661,15 @@ where pub async fn with_candid( input: Input, f: impl FnOnce(Vec) -> Fut, -) -> Result +) -> Result where Input: ArgumentEncoder, Output: for<'a> ArgumentDecoder<'a>, - Fut: Future>, + Fut: Future, RejectResponse>>, { let in_bytes = encode_args(input).expect("failed to encode args"); - match f(in_bytes).await { - Ok(WasmResult::Reply(out_bytes)) => Ok(decode_args(&out_bytes).unwrap_or_else(|e| { + f(in_bytes).await.map(|out_bytes| { + decode_args(&out_bytes).unwrap_or_else(|e| { panic!( "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}", std::any::type_name::(), @@ -1701,10 +1677,8 @@ where out_bytes, String::from_utf8_lossy(&out_bytes), ) - })), - Ok(WasmResult::Reject(message)) => Err(CallError::Reject(message)), - Err(user_error) => Err(CallError::UserError(user_error)), - } + }) + }) } fn setup_tracing(pid: u32) -> Option { diff --git a/packages/pocket-ic/test_canister/canister.did b/packages/pocket-ic/test_canister/canister.did index 242f3de8c78..f0e2766bcc7 100644 --- a/packages/pocket-ic/test_canister/canister.did +++ b/packages/pocket-ic/test_canister/canister.did @@ -114,4 +114,8 @@ service : { execute_many_instructions : (nat64) -> (); canister_log : (text) -> (); time : () -> (nat64) query; + reject_query : () -> () query; + reject_update : () -> (); + trap_query : () -> () query; + trap_update : () -> (); } diff --git a/packages/pocket-ic/test_canister/src/canister.rs b/packages/pocket-ic/test_canister/src/canister.rs index 8f8fe0b6a2d..239803ea47b 100644 --- a/packages/pocket-ic/test_canister/src/canister.rs +++ b/packages/pocket-ic/test_canister/src/canister.rs @@ -1,5 +1,5 @@ use candid::{define_function, CandidType, Principal}; -use ic_cdk::api::call::RejectionCode; +use ic_cdk::api::call::{accept_message, arg_data_raw, reject, RejectionCode}; use ic_cdk::api::instruction_counter; use ic_cdk::api::management_canister::ecdsa::{ ecdsa_public_key as ic_cdk_ecdsa_public_key, sign_with_ecdsa as ic_cdk_sign_with_ecdsa, @@ -9,7 +9,7 @@ use ic_cdk::api::management_canister::http_request::{ http_request as canister_http_outcall, CanisterHttpRequestArgument, HttpMethod, HttpResponse, TransformArgs, TransformContext, TransformFunc, }; -use ic_cdk::{query, update}; +use ic_cdk::{inspect_message, query, trap, update}; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; @@ -314,4 +314,37 @@ fn time() -> u64 { ic_cdk::api::time() } +// reject responses + +#[inspect_message] +fn inspect_message() { + let arg_data = arg_data_raw(); + if arg_data == b"trap" { + trap("trap in inspect message"); + } else if arg_data == b"skip" { + } else { + accept_message(); + } +} + +#[query(manual_reply = true)] +fn reject_query() { + reject("reject in query method"); +} + +#[update(manual_reply = true)] +fn reject_update() { + reject("reject in update method"); +} + +#[query] +fn trap_query() { + trap("trap in query method"); +} + +#[update] +fn trap_update() { + trap("trap in update method"); +} + fn main() {} diff --git a/packages/pocket-ic/tests/slow.rs b/packages/pocket-ic/tests/slow.rs index 25e94b02537..126cb7db213 100644 --- a/packages/pocket-ic/tests/slow.rs +++ b/packages/pocket-ic/tests/slow.rs @@ -1,5 +1,5 @@ use candid::{Encode, Principal}; -use pocket_ic::{PocketIc, PocketIcBuilder, UserError, WasmResult}; +use pocket_ic::{PocketIc, PocketIcBuilder, RejectResponse}; use std::time::Duration; // 200T cycles @@ -15,7 +15,7 @@ fn execute_many_instructions( instructions: u64, dts_rounds: u64, system_subnet: bool, -) -> Result { +) -> Result, RejectResponse> { // Create a canister. let t0 = pic.get_time(); let can_id = pic.create_canister(); @@ -90,7 +90,7 @@ fn instruction_limit_exceeded() { let instructions = 42_000_000_000_u64; let dts_rounds = 20; // instruction limit exceeded after 20 rounds let res = execute_many_instructions(&pic, instructions, dts_rounds, false).unwrap_err(); - assert!(res.description.contains( + assert!(res.reject_message.contains( "Canister exceeded the limit of 40000000000 instructions for single message execution." )); } diff --git a/packages/pocket-ic/tests/tests.rs b/packages/pocket-ic/tests/tests.rs index 196a11777e1..a872790851b 100644 --- a/packages/pocket-ic/tests/tests.rs +++ b/packages/pocket-ic/tests/tests.rs @@ -3,7 +3,7 @@ use ic_certification::Label; use ic_transport_types::Envelope; use ic_transport_types::EnvelopeContent::ReadState; use pocket_ic::management_canister::{ - CanisterId, CanisterIdRecord, CanisterInstallMode, CanisterSettings, EcdsaPublicKeyResult, + CanisterIdRecord, CanisterInstallMode, CanisterSettings, EcdsaPublicKeyResult, HttpRequestResult, ProvisionalCreateCanisterWithCyclesArgs, SchnorrAlgorithm, SchnorrPublicKeyArgsKeyId, SchnorrPublicKeyResult, SignWithBip341Aux, SignWithSchnorrAux, }; @@ -13,7 +13,7 @@ use pocket_ic::{ RawEffectivePrincipal, SubnetKind, }, query_candid, update_candid, DefaultEffectiveCanisterIdError, ErrorCode, IngressStatusResult, - PocketIc, PocketIcBuilder, WasmResult, + PocketIc, PocketIcBuilder, RejectCode, }; #[cfg(unix)] use reqwest::blocking::Client; @@ -35,39 +35,48 @@ enum RejectionCode { Unknown, } +// Create a counter canister and charge it with 2T cycles. +fn deploy_counter_canister(pic: &PocketIc) -> Principal { + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, INIT_CYCLES); + pic.install_canister(canister_id, counter_wasm(), vec![], None); + canister_id +} + +// Call a method on the counter canister as the anonymous principal. +fn call_counter_canister(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec { + pic.update_call( + canister_id, + Principal::anonymous(), + method, + encode_one(()).unwrap(), + ) + .expect("Failed to call counter canister") +} + #[test] fn test_counter_canister() { let pic = PocketIc::new(); + let canister_id = deploy_counter_canister(&pic); - // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); - - // Install the counter canister wasm file on the canister. - let counter_wasm = counter_wasm(); - pic.install_canister(can_id, counter_wasm, vec![], None); - - // Make some calls to the canister. - let reply = call_counter_can(&pic, can_id, "read"); - assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 0])); - let reply = call_counter_can(&pic, can_id, "write"); - assert_eq!(reply, WasmResult::Reply(vec![1, 0, 0, 0])); - let reply = call_counter_can(&pic, can_id, "write"); - assert_eq!(reply, WasmResult::Reply(vec![2, 0, 0, 0])); - let reply = call_counter_can(&pic, can_id, "read"); - assert_eq!(reply, WasmResult::Reply(vec![2, 0, 0, 0])); + // Make some calls to the counter canister. + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, vec![0, 0, 0, 0]); + let reply = call_counter_canister(&pic, canister_id, "write"); + assert_eq!(reply, vec![1, 0, 0, 0]); + let reply = call_counter_canister(&pic, canister_id, "write"); + assert_eq!(reply, vec![2, 0, 0, 0]); + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, vec![2, 0, 0, 0]); } fn counter_wasm() -> Vec { const COUNTER_WAT: &str = r#" (module (import "ic0" "msg_reply" (func $msg_reply)) - (import "ic0" "msg_reply_data_append" - (func $msg_reply_data_append (param i32 i32))) + (import "ic0" "msg_reply_data_append" (func $msg_reply_data_append (param i32 i32))) (func $write - (i32.store - (i32.const 0) - (i32.add (i32.load (i32.const 0)) (i32.const 1))) + (i32.store (i32.const 0) (i32.add (i32.load (i32.const 0)) (i32.const 1))) (call $read)) (func $read (call $msg_reply_data_append @@ -81,16 +90,6 @@ fn counter_wasm() -> Vec { wat::parse_str(COUNTER_WAT).unwrap() } -fn call_counter_can(ic: &PocketIc, can_id: CanisterId, method: &str) -> WasmResult { - ic.update_call( - can_id, - Principal::anonymous(), - method, - encode_one(()).unwrap(), - ) - .expect("Failed to call counter canister") -} - #[test] fn test_create_canister_with_id() { let pic = PocketIcBuilder::new() @@ -283,22 +282,20 @@ fn test_routing_with_multiple_subnets() { let canister_id_2 = pic.create_canister_on_subnet(None, None, subnet_id_2); pic.add_cycles(canister_id_1, INIT_CYCLES); pic.add_cycles(canister_id_2, INIT_CYCLES); - - let counter_wasm = counter_wasm(); - pic.install_canister(canister_id_1, counter_wasm.clone(), vec![], None); - pic.install_canister(canister_id_2, counter_wasm.clone(), vec![], None); + pic.install_canister(canister_id_1, counter_wasm(), vec![], None); + pic.install_canister(canister_id_2, counter_wasm(), vec![], None); // Call canister 1 on subnet 1. - let reply = call_counter_can(&pic, canister_id_1, "read"); - assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 0])); - let reply = call_counter_can(&pic, canister_id_1, "write"); - assert_eq!(reply, WasmResult::Reply(vec![1, 0, 0, 0])); + let reply = call_counter_canister(&pic, canister_id_1, "read"); + assert_eq!(reply, vec![0, 0, 0, 0]); + let reply = call_counter_canister(&pic, canister_id_1, "write"); + assert_eq!(reply, vec![1, 0, 0, 0]); // Call canister 2 on subnet 2. - let reply = call_counter_can(&pic, canister_id_2, "read"); - assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 0])); - let reply = call_counter_can(&pic, canister_id_2, "write"); - assert_eq!(reply, WasmResult::Reply(vec![1, 0, 0, 0])); + let reply = call_counter_canister(&pic, canister_id_2, "read"); + assert_eq!(reply, vec![0, 0, 0, 0]); + let reply = call_counter_canister(&pic, canister_id_2, "write"); + assert_eq!(reply, vec![1, 0, 0, 0]); // Creating a canister without specifying a subnet should still work. let _canister_id = pic.create_canister(); @@ -333,7 +330,7 @@ fn test_multiple_large_xnet_payloads() { // Self-calls with 10M and xnet-calls with up to 2M arguments work just fine // and return the length of the blob sent in the inter-canister call. match xnet_result { - Ok(WasmResult::Reply(reply)) => { + Ok(reply) => { let blob_len = Decode!(&reply, usize).unwrap(); assert_eq!(blob_len, size); } @@ -342,8 +339,8 @@ fn test_multiple_large_xnet_payloads() { } else { // An inter-canister call to a different subnet with 10M argument traps. match xnet_result { - Err(user_error) => { - assert_eq!(user_error.code, ErrorCode::CanisterCalledTrap); + Err(reject_response) => { + assert_eq!(reject_response.error_code, ErrorCode::CanisterCalledTrap); } _ => panic!("Unexpected update call result: {:?}", xnet_result), }; @@ -514,11 +511,7 @@ fn test_get_subnet_of_canister() { #[test] fn test_set_and_get_stable_memory_not_compressed() { let pic = PocketIc::new(); - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, INIT_CYCLES); - - let counter_wasm = counter_wasm(); - pic.install_canister(canister_id, counter_wasm, vec![], None); + let canister_id = deploy_counter_canister(&pic); let data = "deadbeef".as_bytes().to_vec(); pic.set_stable_memory(canister_id, data.clone(), BlobCompression::NoCompression); @@ -530,10 +523,7 @@ fn test_set_and_get_stable_memory_not_compressed() { #[test] fn test_set_and_get_stable_memory_compressed() { let pic = PocketIc::new(); - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, INIT_CYCLES); - let counter_wasm = counter_wasm(); - pic.install_canister(canister_id, counter_wasm, vec![], None); + let canister_id = deploy_counter_canister(&pic); let data = "decafbad".as_bytes().to_vec(); let mut compressed_data = Vec::new(); @@ -665,11 +655,7 @@ fn test_inspect_message() { #[test] fn test_too_large_call() { let pic = PocketIc::new(); - - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, INIT_CYCLES); - let counter_wasm = counter_wasm(); - pic.install_canister(canister_id, counter_wasm, vec![], None); + let canister_id = deploy_counter_canister(&pic); const MAX_INGRESS_MESSAGE_ARG_SIZE: usize = 2097152; pic.update_call( @@ -700,26 +686,23 @@ async fn test_create_and_drop_instances_async() { async fn test_counter_canister_async() { let pic = pocket_ic::nonblocking::PocketIc::new().await; - // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister().await; - pic.add_cycles(can_id, INIT_CYCLES).await; - - // Install the counter canister wasm file on the canister. - let counter_wasm = counter_wasm(); - pic.install_canister(can_id, counter_wasm, vec![], None) + // Create a counter canister and charge it with 2T cycles. + let canister_id = pic.create_canister().await; + pic.add_cycles(canister_id, INIT_CYCLES).await; + pic.install_canister(canister_id, counter_wasm(), vec![], None) .await; // Make some calls to the canister. let reply = pic .update_call( - can_id, + canister_id, Principal::anonymous(), "read", encode_one(()).unwrap(), ) .await .expect("Failed to call counter canister"); - assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 0])); + assert_eq!(reply, vec![0, 0, 0, 0]); // Drop the PocketIc instance. pic.drop().await; @@ -753,48 +736,38 @@ fn install_very_large_wasm() { let pic = PocketIcBuilder::new().with_application_subnet().build(); // Create a canister. - let can_id = pic.create_canister(); + let canister_id = pic.create_canister(); // Charge the canister with 2T cycles. - pic.add_cycles(can_id, 100 * INIT_CYCLES); + pic.add_cycles(canister_id, 100 * INIT_CYCLES); // Install the very large canister wasm on the canister. let wasm_module = very_large_wasm(5_000_000); assert!(wasm_module.len() >= 5_000_000); - pic.install_canister(can_id, wasm_module, vec![], None); + pic.install_canister(canister_id, wasm_module, vec![], None); // Update call on the newly installed canister should succeed // and return 4 bytes of the large data section. let res = pic - .update_call(can_id, Principal::anonymous(), "read", vec![]) + .update_call(canister_id, Principal::anonymous(), "read", vec![]) .unwrap(); - match res { - WasmResult::Reply(data) => assert_eq!(data, vec![b'X'; 4]), - _ => panic!("Unexpected update call response: {:?}", res), - }; + assert_eq!(res, vec![b'X'; 4]); } #[test] fn test_uninstall_canister() { let pic = PocketIc::new(); - - // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); - - // Install the counter canister wasm file on the canister. - let counter_wasm = counter_wasm(); - pic.install_canister(can_id, counter_wasm, vec![], None); + let canister_id = deploy_counter_canister(&pic); // The module hash should be set after the canister is installed. - let status = pic.canister_status(can_id, None).unwrap(); + let status = pic.canister_status(canister_id, None).unwrap(); assert!(status.module_hash.is_some()); // Uninstall the canister. - pic.uninstall_canister(can_id, None).unwrap(); + pic.uninstall_canister(canister_id, None).unwrap(); // The module hash should be unset after the canister is uninstalled. - let status = pic.canister_status(can_id, None).unwrap(); + let status = pic.canister_status(canister_id, None).unwrap(); assert!(status.module_hash.is_none()); } @@ -803,11 +776,11 @@ fn test_update_canister_settings() { let pic = PocketIc::new(); // Create a canister and charge it with 200T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, 100 * INIT_CYCLES); + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, 100 * INIT_CYCLES); // The compute allocation of the canister should be zero. - let status = pic.canister_status(can_id, None).unwrap(); + let status = pic.canister_status(canister_id, None).unwrap(); let zero: candid::Nat = 0_u64.into(); assert_eq!(status.settings.compute_allocation, zero); @@ -817,11 +790,11 @@ fn test_update_canister_settings() { compute_allocation: Some(new_compute_allocation.clone()), ..Default::default() }; - pic.update_canister_settings(can_id, None, settings) + pic.update_canister_settings(canister_id, None, settings) .unwrap(); // Check that the compute allocation has been set. - let status = pic.canister_status(can_id, None).unwrap(); + let status = pic.canister_status(canister_id, None).unwrap(); assert_eq!(status.settings.compute_allocation, new_compute_allocation); } @@ -895,7 +868,7 @@ fn test_xnet_call_and_create_canister_with_specified_id() { Encode!(&canister_b).unwrap(), ); match xnet_result { - Ok(WasmResult::Reply(reply)) => { + Ok(reply) => { let identity = Decode!(&reply, String).unwrap(); assert_eq!(identity, canister_b.to_string()); } @@ -1168,18 +1141,18 @@ fn test_canister_http() { let pic = PocketIc::new(); // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, INIT_CYCLES); // Install the test canister wasm file on the canister. let test_wasm = test_canister_wasm(); - pic.install_canister(can_id, test_wasm, vec![], None); + pic.install_canister(canister_id, test_wasm, vec![], None); // Submit an update call to the test canister making a canister http outcall // and mock a canister http outcall response. let call_id = pic .submit_call( - can_id, + canister_id, Principal::anonymous(), "canister_http", encode_one(()).unwrap(), @@ -1214,14 +1187,9 @@ fn test_canister_http() { // Now the test canister will receive the http outcall response // and reply to the ingress message from the test driver. let reply = pic.await_call(call_id).unwrap(); - match reply { - WasmResult::Reply(data) => { - let http_response: Result = - decode_one(&data).unwrap(); - assert_eq!(http_response.unwrap().body, body); - } - WasmResult::Reject(msg) => panic!("Unexpected reject {}", msg), - }; + let http_response: Result = + decode_one(&reply).unwrap(); + assert_eq!(http_response.unwrap().body, body); } #[test] @@ -1229,12 +1197,12 @@ fn test_canister_http_with_transform() { let pic = PocketIc::new(); // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, INIT_CYCLES); // Install the test canister wasm file on the canister. let test_wasm = test_canister_wasm(); - pic.install_canister(can_id, test_wasm, vec![], None); + pic.install_canister(canister_id, test_wasm, vec![], None); // Submit an update call to the test canister making a canister http outcall // with a transform function (clearing http response headers and setting @@ -1242,7 +1210,7 @@ fn test_canister_http_with_transform() { // and mock a canister http outcall response. let call_id = pic .submit_call( - can_id, + canister_id, Principal::anonymous(), "canister_http_with_transform", encode_one(()).unwrap(), @@ -1276,17 +1244,12 @@ fn test_canister_http_with_transform() { // Now the test canister will receive the http outcall response // and reply to the ingress message from the test driver. let reply = pic.await_call(call_id).unwrap(); - match reply { - WasmResult::Reply(data) => { - let http_response: HttpRequestResult = decode_one(&data).unwrap(); - // http response headers are cleared by the transform function - assert!(http_response.headers.is_empty()); - // mocked non-empty response body is transformed to the transform context - // by the transform function - assert_eq!(http_response.body, b"this is my transform context".to_vec()); - } - WasmResult::Reject(msg) => panic!("Unexpected reject {}", msg), - }; + let http_response: HttpRequestResult = decode_one(&reply).unwrap(); + // http response headers are cleared by the transform function + assert!(http_response.headers.is_empty()); + // mocked non-empty response body is transformed to the transform context + // by the transform function + assert_eq!(http_response.body, b"this is my transform context".to_vec()); } #[test] @@ -1294,18 +1257,18 @@ fn test_canister_http_with_diverging_responses() { let pic = PocketIc::new(); // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, INIT_CYCLES); // Install the test canister wasm file on the canister. let test_wasm = test_canister_wasm(); - pic.install_canister(can_id, test_wasm, vec![], None); + pic.install_canister(canister_id, test_wasm, vec![], None); // Submit an update call to the test canister making a canister http outcall // and mock diverging canister http outcall responses. let call_id = pic .submit_call( - can_id, + canister_id, Principal::anonymous(), "canister_http", encode_one(()).unwrap(), @@ -1343,17 +1306,12 @@ fn test_canister_http_with_diverging_responses() { // and reply to the ingress message from the test driver // relaying the error. let reply = pic.await_call(call_id).unwrap(); - match reply { - WasmResult::Reply(data) => { - let http_response: Result = - decode_one(&data).unwrap(); - let (reject_code, err) = http_response.unwrap_err(); - assert!(matches!(reject_code, RejectionCode::SysTransient)); - let expected = "No consensus could be reached. Replicas had different responses. Details: request_id: 0, timeout: 1620328930000000005, hashes: [98387cc077af9cff2ef439132854e91cb074035bb76e2afb266960d8e3beaf11: 2], [6a2fa8e54fb4bbe62cde29f7531223d9fcf52c21c03500c1060a5f893ed32d2e: 2], [3e9ec98abf56ef680bebb14309858ede38f6fde771cd4c04cda8f066dc2810db: 2], [2c14e77f18cd990676ae6ce0d7eb89c0af9e1a66e17294b5f0efa68422bba4cb: 2], [2843e4133f673571ff919808d3ca542cc54aaf288c702944e291f0e4fafffc69: 2], [1c4ad84926c36f1fbc634a0dc0535709706f7c48f0c6ebd814fe514022b90671: 2], [7bf80e2f02011ab0a7836b526546e75203b94e856d767c9df4cb0c19baf34059: 1]"; - assert_eq!(err, expected); - } - WasmResult::Reject(msg) => panic!("Unexpected reject {}", msg), - }; + let http_response: Result = + decode_one(&reply).unwrap(); + let (reject_code, err) = http_response.unwrap_err(); + assert!(matches!(reject_code, RejectionCode::SysTransient)); + let expected = "No consensus could be reached. Replicas had different responses. Details: request_id: 0, timeout: 1620328930000000005, hashes: [98387cc077af9cff2ef439132854e91cb074035bb76e2afb266960d8e3beaf11: 2], [6a2fa8e54fb4bbe62cde29f7531223d9fcf52c21c03500c1060a5f893ed32d2e: 2], [3e9ec98abf56ef680bebb14309858ede38f6fde771cd4c04cda8f066dc2810db: 2], [2c14e77f18cd990676ae6ce0d7eb89c0af9e1a66e17294b5f0efa68422bba4cb: 2], [2843e4133f673571ff919808d3ca542cc54aaf288c702944e291f0e4fafffc69: 2], [1c4ad84926c36f1fbc634a0dc0535709706f7c48f0c6ebd814fe514022b90671: 2], [7bf80e2f02011ab0a7836b526546e75203b94e856d767c9df4cb0c19baf34059: 1]"; + assert_eq!(err, expected); } #[test] @@ -1362,17 +1320,17 @@ fn test_canister_http_with_one_additional_response() { let pic = PocketIc::new(); // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, INIT_CYCLES); // Install the test canister wasm file on the canister. let test_wasm = test_canister_wasm(); - pic.install_canister(can_id, test_wasm, vec![], None); + pic.install_canister(canister_id, test_wasm, vec![], None); // Submit an update call to the test canister making a canister http outcall // and mock diverging canister http outcall responses. pic.submit_call( - can_id, + canister_id, Principal::anonymous(), "canister_http", encode_one(()).unwrap(), @@ -1410,18 +1368,18 @@ fn test_canister_http_timeout() { let pic = PocketIc::new(); // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, INIT_CYCLES); // Install the test canister wasm file on the canister. let test_wasm = test_canister_wasm(); - pic.install_canister(can_id, test_wasm, vec![], None); + pic.install_canister(canister_id, test_wasm, vec![], None); // Submit an update call to the test canister making a canister http outcall // and mock a canister http outcall response. let call_id = pic .submit_call( - can_id, + canister_id, Principal::anonymous(), "canister_http", encode_one(()).unwrap(), @@ -1446,24 +1404,18 @@ fn test_canister_http_timeout() { // Now the test canister will receive the http outcall response // and reply to the ingress message from the test driver. let reply = pic.await_call(call_id).unwrap(); - match reply { - WasmResult::Reply(data) => { - let http_response: Result = - decode_one(&data).unwrap(); - let (reject_code, err) = http_response.unwrap_err(); - match reject_code { - RejectionCode::SysTransient => (), - _ => panic!("Unexpected reject code {:?}", reject_code), - }; - assert_eq!(err, "Canister http request timed out"); - } - WasmResult::Reject(msg) => panic!("Unexpected reject {}", msg), + let http_response: Result = + decode_one(&reply).unwrap(); + let (reject_code, err) = http_response.unwrap_err(); + match reject_code { + RejectionCode::SysTransient => (), + _ => panic!("Unexpected reject code {:?}", reject_code), }; + assert_eq!(err, "Canister http request timed out"); } #[test] fn subnet_metrics() { - const INIT_CYCLES: u128 = 2_000_000_000_000; let pic = PocketIcBuilder::new().with_application_subnet().build(); let topology = pic.topology(); @@ -1473,17 +1425,13 @@ fn subnet_metrics() { .get_subnet_metrics(Principal::management_canister()) .is_none()); - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, INIT_CYCLES); - pic.install_canister(canister_id, counter_wasm(), vec![], None); + deploy_counter_canister(&pic); let metrics = pic.get_subnet_metrics(app_subnet).unwrap(); assert_eq!(metrics.num_canisters, 1); assert!((1 << 16) < metrics.canister_state_bytes && metrics.canister_state_bytes < (1 << 17)); - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, INIT_CYCLES); - pic.install_canister(canister_id, counter_wasm(), vec![], None); + let canister_id = deploy_counter_canister(&pic); let metrics = pic.get_subnet_metrics(app_subnet).unwrap(); assert_eq!(metrics.num_canisters, 2); @@ -1789,17 +1737,13 @@ fn get_controllers_of_nonexisting_canister() { #[test] fn test_canister_snapshots() { let pic = PocketIc::new(); - - // We deploy the counter canister. - let canister_id = pic.create_canister(); - pic.add_cycles(canister_id, INIT_CYCLES); - pic.install_canister(canister_id, counter_wasm(), vec![], None); + let canister_id = deploy_counter_canister(&pic); // We bump the counter to make the counter different from its initial value. - let reply = call_counter_can(&pic, canister_id, "write"); - assert_eq!(reply, WasmResult::Reply(1_u32.to_le_bytes().to_vec())); - let reply = call_counter_can(&pic, canister_id, "read"); - assert_eq!(reply, WasmResult::Reply(1_u32.to_le_bytes().to_vec())); + let reply = call_counter_canister(&pic, canister_id, "write"); + assert_eq!(reply, 1_u32.to_le_bytes().to_vec()); + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, 1_u32.to_le_bytes().to_vec()); // We haven't taken any snapshot so far and thus listing snapshots yields an empty result. let snapshots = pic.list_canister_snapshots(canister_id, None).unwrap(); @@ -1821,10 +1765,10 @@ fn test_canister_snapshots() { ); // We bump the counter once more to test loading snapshots in a subsequent step. - let reply = call_counter_can(&pic, canister_id, "write"); - assert_eq!(reply, WasmResult::Reply(2_u32.to_le_bytes().to_vec())); - let reply = call_counter_can(&pic, canister_id, "read"); - assert_eq!(reply, WasmResult::Reply(2_u32.to_le_bytes().to_vec())); + let reply = call_counter_canister(&pic, canister_id, "write"); + assert_eq!(reply, 2_u32.to_le_bytes().to_vec()); + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, 2_u32.to_le_bytes().to_vec()); // We load the snapshot (it is recommended to only load a snapshot on a stopped canister). pic.stop_canister(canister_id, None).unwrap(); @@ -1833,14 +1777,14 @@ fn test_canister_snapshots() { pic.start_canister(canister_id, None).unwrap(); // We verify that the snapshot was successfully loaded. - let reply = call_counter_can(&pic, canister_id, "read"); - assert_eq!(reply, WasmResult::Reply(1_u32.to_le_bytes().to_vec())); + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, 1_u32.to_le_bytes().to_vec()); // We bump the counter again. - let reply = call_counter_can(&pic, canister_id, "write"); - assert_eq!(reply, WasmResult::Reply(2_u32.to_le_bytes().to_vec())); - let reply = call_counter_can(&pic, canister_id, "read"); - assert_eq!(reply, WasmResult::Reply(2_u32.to_le_bytes().to_vec())); + let reply = call_counter_canister(&pic, canister_id, "write"); + assert_eq!(reply, 2_u32.to_le_bytes().to_vec()); + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, 2_u32.to_le_bytes().to_vec()); // We take one more snapshot: since we already have an active snapshot, // taking another snapshot fails unless we specify the active snapshot to be replaced. @@ -2010,40 +1954,33 @@ fn make_live_twice() { #[test] fn create_instance_from_existing() { let pic = PocketIc::new(); - - // Create a canister and charge it with 2T cycles. - let can_id = pic.create_canister(); - pic.add_cycles(can_id, INIT_CYCLES); - - // Install the counter canister wasm file on the canister. - let counter_wasm = counter_wasm(); - pic.install_canister(can_id, counter_wasm, vec![], None); + let canister_id = deploy_counter_canister(&pic); // Bump and check the counter value; - let reply = call_counter_can(&pic, can_id, "write"); - assert_eq!(reply, WasmResult::Reply(vec![1, 0, 0, 0])); - let reply = call_counter_can(&pic, can_id, "read"); - assert_eq!(reply, WasmResult::Reply(vec![1, 0, 0, 0])); + let reply = call_counter_canister(&pic, canister_id, "write"); + assert_eq!(reply, vec![1, 0, 0, 0]); + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, vec![1, 0, 0, 0]); // Create a new PocketIC handle to the existing PocketIC instance. let pic_handle = PocketIc::new_from_existing_instance(pic.get_server_url(), pic.instance_id(), None); // Bump and check the counter value; - let reply = call_counter_can(&pic_handle, can_id, "write"); - assert_eq!(reply, WasmResult::Reply(vec![2, 0, 0, 0])); - let reply = call_counter_can(&pic_handle, can_id, "read"); - assert_eq!(reply, WasmResult::Reply(vec![2, 0, 0, 0])); + let reply = call_counter_canister(&pic_handle, canister_id, "write"); + assert_eq!(reply, vec![2, 0, 0, 0]); + let reply = call_counter_canister(&pic_handle, canister_id, "read"); + assert_eq!(reply, vec![2, 0, 0, 0]); // Drop the newly created PocketIC handle. // This should not delete the existing PocketIC instance. drop(pic_handle); // Bump and check the counter value; - let reply = call_counter_can(&pic, can_id, "write"); - assert_eq!(reply, WasmResult::Reply(vec![3, 0, 0, 0])); - let reply = call_counter_can(&pic, can_id, "read"); - assert_eq!(reply, WasmResult::Reply(vec![3, 0, 0, 0])); + let reply = call_counter_canister(&pic, canister_id, "write"); + assert_eq!(reply, vec![3, 0, 0, 0]); + let reply = call_counter_canister(&pic, canister_id, "read"); + assert_eq!(reply, vec![3, 0, 0, 0]); } #[test] @@ -2078,10 +2015,7 @@ fn ingress_status() { IngressStatusResult::Success(result) => result.unwrap(), status => panic!("Unexpected ingress status: {:?}", status), }; - let principal = match reply { - WasmResult::Reply(data) => Decode!(&data, String).unwrap(), - WasmResult::Reject(err) => panic!("Unexpected reject: {}", err), - }; + let principal = Decode!(&reply, String).unwrap(); assert_eq!(principal, canister_id.to_string()); // now that the ingress status is available, the caller must match @@ -2165,10 +2099,7 @@ fn await_call_no_ticks() { .unwrap(); let result = pic.await_call_no_ticks(msg_id).unwrap(); - let principal = match result { - WasmResult::Reply(data) => Decode!(&data, String).unwrap(), - WasmResult::Reject(err) => panic!("Unexpected reject: {}", err), - }; + let principal = Decode!(&result, String).unwrap(); assert_eq!(principal, canister_id.to_string()); } @@ -2203,3 +2134,89 @@ fn many_intersubnet_calls() { pic.await_call(msg_id).unwrap(); } } + +#[test] +fn test_reject_response_type() { + let pic = PocketIc::new(); + + // We create a test canister. + let canister = pic.create_canister(); + pic.add_cycles(canister, INIT_CYCLES); + pic.install_canister(canister, test_canister_wasm(), vec![], None); + + for certified in [true, false] { + for action in ["reject", "trap"] { + for method in ["query", "update"] { + // updates are always certified + if !certified && method == "update" { + continue; + } + let method_name = format!("{}_{}", action, method); + let (err, msg_id) = if certified { + let msg_id = pic + .submit_call( + canister, + Principal::anonymous(), + &method_name, + Encode!(&()).unwrap(), + ) + .unwrap(); + let err = pic.await_call(msg_id.clone()).unwrap_err(); + (err, Some(msg_id)) + } else { + let err = pic + .query_call( + canister, + Principal::anonymous(), + &method_name, + Encode!(&()).unwrap(), + ) + .unwrap_err(); + (err, None) + }; + if let Some(msg_id) = msg_id { + let ingress_status_err = match pic.ingress_status(msg_id, None) { + IngressStatusResult::Success(result) => result.unwrap_err(), + status => panic!("Unexpected ingress status: {:?}", status), + }; + assert_eq!(ingress_status_err, err); + } + if action == "reject" { + assert_eq!(err.reject_code, RejectCode::CanisterReject); + assert_eq!(err.error_code, ErrorCode::CanisterRejectedMessage); + } else { + assert_eq!(action, "trap"); + assert_eq!(err.reject_code, RejectCode::CanisterError); + assert_eq!(err.error_code, ErrorCode::CanisterCalledTrap); + } + assert!(err + .reject_message + .contains(&format!("{} in {} method", action, method))); + assert_eq!(err.certified, certified); + } + } + } + + for action in [b"trap", b"skip"] { + let err = pic + .submit_call( + canister, + Principal::anonymous(), + "trap_update", + action.to_vec(), + ) + .unwrap_err(); + if action == b"trap" { + assert_eq!(err.reject_code, RejectCode::CanisterError); + assert!(err.reject_message.contains("trap in inspect message")); + assert_eq!(err.error_code, ErrorCode::CanisterCalledTrap); + } else { + assert_eq!(action, b"skip"); + assert_eq!(err.reject_code, RejectCode::CanisterReject); + assert!(err.reject_message.contains("Canister rejected the message")); + assert_eq!(err.error_code, ErrorCode::CanisterRejectedMessage); + } + // inspect message is always uncertified + assert!(!err.certified); + } +} diff --git a/rs/bitcoin/checker/tests/tests.rs b/rs/bitcoin/checker/tests/tests.rs index 33df765e1de..018c78d2b4b 100644 --- a/rs/bitcoin/checker/tests/tests.rs +++ b/rs/bitcoin/checker/tests/tests.rs @@ -1,4 +1,4 @@ -use candid::{decode_one, CandidType, Deserialize, Encode, Principal}; +use candid::{decode_one, Encode, Principal}; use ic_base_types::PrincipalId; use ic_btc_checker::{ blocklist, get_tx_cycle_cost, BtcNetwork, CheckAddressArgs, CheckAddressResponse, CheckArg, @@ -17,7 +17,7 @@ use pocket_ic::{ CanisterHttpHeader, CanisterHttpReject, CanisterHttpReply, CanisterHttpRequest, CanisterHttpResponse, MockCanisterHttpResponse, RawMessageId, }, - query_candid, PocketIc, PocketIcBuilder, UserError, WasmResult, + query_candid, PocketIc, PocketIcBuilder, RejectResponse, }; use regex::Regex; use std::str::FromStr; @@ -100,7 +100,7 @@ impl Setup { method: &str, args: Vec, cycles: u128, - ) -> Result { + ) -> Result { let payload = wasm() .call_with_cycles( PrincipalId(self.btc_checker_canister), @@ -116,13 +116,6 @@ impl Setup { } } -fn decode<'a, T: CandidType + Deserialize<'a>>(result: &'a WasmResult) -> T { - match result { - WasmResult::Reply(bytes) => decode_one(bytes).unwrap(), - WasmResult::Reject(msg) => panic!("unexpected reject: {}", msg), - } -} - #[test] fn test_get_tx_cycle_cost() { assert_eq!( @@ -366,7 +359,7 @@ fn test_check_transaction_passed() { .expect("the fetch request didn't finish"); assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Passed )); @@ -413,7 +406,7 @@ fn test_check_transaction_passed() { .expect("the fetch request didn't finish"); assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Failed(addresses) if addresses.is_empty() ),); let cycles_after = env.cycle_balance(setup.caller); @@ -454,7 +447,7 @@ fn test_check_transaction_passed() { .expect("the fetch request didn't finish"); assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Passed ),); let cycles_after = env.cycle_balance(setup.caller); @@ -508,7 +501,7 @@ fn test_check_transaction_error() { .await_call(call_id) .expect("the fetch request didn't finish"); assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::NotEnoughCycles), )); @@ -526,7 +519,7 @@ fn test_check_transaction_error() { .await_call(call_id) .expect("the fetch request didn't finish"); assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::NotEnoughCycles), )); @@ -564,7 +557,7 @@ fn test_check_transaction_error() { .expect("the fetch request didn't finish"); // 500 error is retriable assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::Retriable( CheckTransactionRetriable::TransientInternalError(msg) )) if msg.contains("received code 500") @@ -604,7 +597,7 @@ fn test_check_transaction_error() { .expect("the fetch request didn't finish"); // 404 error is retriable too assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::Retriable( CheckTransactionRetriable::TransientInternalError(msg) )) if msg.contains("received code 404") @@ -643,7 +636,7 @@ fn test_check_transaction_error() { .expect("the fetch request didn't finish"); // Reject error is retriable too assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::Retriable( CheckTransactionRetriable::TransientInternalError(msg) )) if msg.contains("Failed to directly connect") @@ -683,7 +676,7 @@ fn test_check_transaction_error() { .expect("the fetch request didn't finish"); // malformated tx error is retriable assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::Retriable( CheckTransactionRetriable::TransientInternalError(msg) )) if msg.contains("TxEncoding") @@ -710,7 +703,7 @@ fn test_check_transaction_error() { .await_call(call_id) .expect("the fetch request didn't finish"); assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::Error( CheckTransactionIrrecoverableError::InvalidTransactionId(_) )) @@ -741,7 +734,7 @@ fn test_check_transaction_error() { .await_call(call_id) .expect("the fetch request didn't finish"); assert!(matches!( - decode::(&result), + decode_one(&result).unwrap(), CheckTransactionResponse::Unknown(CheckTransactionStatus::Error( CheckTransactionIrrecoverableError::InvalidTransactionId(_) )) @@ -805,17 +798,15 @@ fn make_http_query>(setup: &Setup, url: U) -> Vec { }; let response = Decode!( - &assert_reply( - setup - .env - .query_call( - setup.btc_checker_canister, - Principal::anonymous(), - "http_request", - Encode!(&request).expect("failed to encode HTTP request"), - ) - .expect("failed to query get_transactions on the ledger") - ), + &setup + .env + .query_call( + setup.btc_checker_canister, + Principal::anonymous(), + "http_request", + Encode!(&request).expect("failed to encode HTTP request"), + ) + .expect("failed to query get_transactions on the ledger"), ic_canisters_http_types::HttpResponse ) .unwrap(); @@ -824,15 +815,6 @@ fn make_http_query>(setup: &Setup, url: U) -> Vec { response.body.into_vec() } -fn assert_reply(result: WasmResult) -> Vec { - match result { - WasmResult::Reply(bytes) => bytes, - WasmResult::Reject(reject) => { - panic!("Expected a successful reply, got a reject: {}", reject) - } - } -} - pub struct MetricsAssert { metrics: Vec, } diff --git a/rs/boundary_node/rate_limits/integration_tests/src/pocket_ic_helpers.rs b/rs/boundary_node/rate_limits/integration_tests/src/pocket_ic_helpers.rs index cfbce8ae134..02c2ea31af4 100644 --- a/rs/boundary_node/rate_limits/integration_tests/src/pocket_ic_helpers.rs +++ b/rs/boundary_node/rate_limits/integration_tests/src/pocket_ic_helpers.rs @@ -7,9 +7,7 @@ use ic_nns_test_utils::common::{ build_mainnet_registry_wasm, build_registry_wasm, NnsInitPayloadsBuilder, }; use ic_registry_transport::pb::v1::RegistryAtomicMutateRequest; -use pocket_ic::{ - management_canister::CanisterSettings, nonblocking::PocketIc, PocketIcBuilder, WasmResult, -}; +use pocket_ic::{management_canister::CanisterSettings, nonblocking::PocketIc, PocketIcBuilder}; use rate_limits_api::InitArg; use serde::de::DeserializeOwned; @@ -115,11 +113,6 @@ pub async fn canister_call( _ => panic!("{method_type} is not allowed"), }; - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to {method} failed: {:#?}", s), - }; - let decoded: R = Decode!(&result, R).unwrap(); Ok(decoded) diff --git a/rs/ledger_suite/icp/archive/tests/tests.rs b/rs/ledger_suite/icp/archive/tests/tests.rs index 9ff626b16cc..c4d8cf1d283 100644 --- a/rs/ledger_suite/icp/archive/tests/tests.rs +++ b/rs/ledger_suite/icp/archive/tests/tests.rs @@ -7,7 +7,7 @@ use ic_ledger_core::Tokens; use ic_ledger_test_utils::build_ledger_archive_wasm; use icp_ledger::Operation::Mint; use icp_ledger::{AccountIdentifier, Block, Memo, Transaction}; -use pocket_ic::{PocketIcBuilder, WasmResult}; +use pocket_ic::PocketIcBuilder; use serde_bytes::ByteBuf; const GENESIS_IN_NANOS_SINCE_UNIX_EPOCH: u64 = 1_620_328_630_000_000_000; @@ -57,12 +57,6 @@ impl Setup { Encode!(&()).expect("should encode empty args"), ) .expect("failed to send remaining_capacity request"); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => { - panic!("Call to remaining_capacity failed: {:#?}", s) - } - }; let res = Decode!(&result, usize).expect("failed to decode usize"); assert_eq!(res, remaining_capacity); } @@ -157,7 +151,7 @@ fn large_http_request() { body: ByteBuf::from(vec![42; 1_000]), }; let http_request_bytes = Encode!(&http_request).unwrap(); - let response = match setup + let response_bytes = setup .pocket_ic .update_call( setup.archive_canister_id.into(), @@ -165,11 +159,8 @@ fn large_http_request() { "http_request", http_request_bytes, ) - .unwrap() - { - WasmResult::Reply(bytes) => Decode!(&bytes, HttpResponse).unwrap(), - WasmResult::Reject(reason) => panic!("Unexpected reject: {}", reason), - }; + .unwrap(); + let response = Decode!(&response_bytes, HttpResponse).unwrap(); assert_eq!(response.status_code, 200); // The anonymous end-user sends a large HTTP request. This should be rejected. @@ -185,5 +176,5 @@ fn large_http_request() { large_http_request_bytes, ) .unwrap_err(); - assert!(err.description.contains("Deserialization Failed")); + assert!(err.reject_message.contains("Deserialization Failed")); } diff --git a/rs/ledger_suite/icp/test_utils/src/pocket_ic_helpers/mod.rs b/rs/ledger_suite/icp/test_utils/src/pocket_ic_helpers/mod.rs index d7da247b6f1..5d4050f78f8 100644 --- a/rs/ledger_suite/icp/test_utils/src/pocket_ic_helpers/mod.rs +++ b/rs/ledger_suite/icp/test_utils/src/pocket_ic_helpers/mod.rs @@ -2,7 +2,7 @@ use candid::{CandidType, Decode, Encode, Nat, Principal}; use ic_base_types::{CanisterId, PrincipalId}; use ic_nns_constants::ALL_NNS_CANISTER_IDS; use pocket_ic::management_canister::CanisterSettings; -use pocket_ic::{PocketIc, WasmResult}; +use pocket_ic::PocketIc; use serde::Deserialize; pub mod index; @@ -58,10 +58,7 @@ where Err(err) => { panic!("{canister_id}.{method} query failed with error {err} (caller: {caller})"); } - Ok(WasmResult::Reject(err)) => { - panic!("{canister_id}.{method} query rejected with error {err} (caller: {caller})"); - } - Ok(WasmResult::Reply(res)) => Decode!(&res, O) + Ok(res) => Decode!(&res, O) .unwrap_or_else(|_| panic!("error decoding response to {} query", method)), } } @@ -83,10 +80,7 @@ where Err(err) => { panic!("{canister_id}.{method} failed with error {err} (caller: {caller})"); } - Ok(WasmResult::Reject(err)) => { - panic!("{canister_id}.{method} rejected with error {err} (caller: {caller})"); - } - Ok(WasmResult::Reply(res)) => Decode!(&res, O) + Ok(res) => Decode!(&res, O) .unwrap_or_else(|_| panic!("error decoding response to {} call", method)), } } diff --git a/rs/ledger_suite/icp/tests/upgrade_downgrade.rs b/rs/ledger_suite/icp/tests/upgrade_downgrade.rs index 9ea817b32d2..107c838d405 100644 --- a/rs/ledger_suite/icp/tests/upgrade_downgrade.rs +++ b/rs/ledger_suite/icp/tests/upgrade_downgrade.rs @@ -22,7 +22,6 @@ use icp_ledger::{ LedgerCanisterUpgradePayload, Memo, Subaccount, TransferArgs, DEFAULT_TRANSFER_FEE, }; use maplit::hashmap; -use pocket_ic::CallError; use pocket_ic::{PocketIc, PocketIcBuilder}; use std::time::Duration; @@ -164,12 +163,9 @@ impl Setup { if should_succeed { panic!("Upgrade should succeed!"); } else { - match e { - CallError::Reject(_) => panic!("Expected UserError!"), - CallError::UserError(user_error) => assert!(user_error - .description - .contains("Trying to downgrade from incompatible version")), - }; + assert!(e + .reject_message + .contains("Trying to downgrade from incompatible version")); } } }; diff --git a/rs/nervous_system/agent/src/pocketic_impl.rs b/rs/nervous_system/agent/src/pocketic_impl.rs index d9e8b814408..a9df27e53f8 100644 --- a/rs/nervous_system/agent/src/pocketic_impl.rs +++ b/rs/nervous_system/agent/src/pocketic_impl.rs @@ -8,9 +8,7 @@ use crate::CallCanisters; #[derive(Error, Debug)] pub enum PocketIcCallError { #[error("pocket_ic error: {0}")] - PocketIc(pocket_ic::UserError), - #[error("canister rejected the request: {0}")] - Reject(String), + PocketIc(pocket_ic::RejectResponse), #[error("canister request could not be encoded: {0}")] CandidEncode(candid::Error), #[error("canister did not respond with the expected response type: {0}")] @@ -47,13 +45,6 @@ impl CallCanisters for PocketIc { } .map_err(PocketIcCallError::PocketIc)?; - match response { - pocket_ic::WasmResult::Reply(reply) => { - let response = candid::decode_one(reply.as_slice()) - .map_err(PocketIcCallError::CandidDecode)?; - Ok(response) - } - pocket_ic::WasmResult::Reject(reject) => Err(PocketIcCallError::Reject(reject)), - } + candid::decode_one(response.as_slice()).map_err(PocketIcCallError::CandidDecode) } } diff --git a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs index d6a1c694f3d..ed173b1eecc 100644 --- a/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs +++ b/rs/nervous_system/integration_tests/src/pocket_ic_helpers.rs @@ -65,7 +65,7 @@ use itertools::Itertools; use maplit::btreemap; use pocket_ic::{ management_canister::CanisterSettings, nonblocking::PocketIc, ErrorCode, PocketIcBuilder, - UserError, WasmResult, + RejectResponse, }; use prost::Message; use rust_decimal::prelude::ToPrimitive; @@ -838,15 +838,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(reply) => reply, - WasmResult::Reject(reject) => { - panic!( - "list_neurons was rejected by the NNS governance canister: {:#?}", - reject - ) - } - }; Decode!(&result, ListNeuronsResponse).unwrap() } @@ -871,10 +862,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to (NNS) manage_neuron failed: {:#?}", s), - }; Decode!(&result, ManageNeuronResponse).unwrap() } @@ -913,7 +900,7 @@ pub mod nns { pocket_ic: &PocketIc, proposal_id: u64, sender: PrincipalId, - ) -> Result { + ) -> Result { pocket_ic .query_call( GOVERNANCE_CANISTER_ID.into(), @@ -922,17 +909,7 @@ pub mod nns { Encode!(&proposal_id).unwrap(), ) .await - .map(|result| match result { - WasmResult::Reply(reply) => { - Decode!(&reply, Option).unwrap().unwrap() - } - WasmResult::Reject(reject) => { - panic!( - "get_proposal_info was rejected by the NNS governance canister: {:#?}", - reject - ) - } - }) + .map(|result| Decode!(&result, Option).unwrap().unwrap()) } pub async fn wait_for_proposal_execution( @@ -956,7 +933,7 @@ pub mod nns { // more attempts to get the proposal info to find out if the proposal // actually got executed. let is_benign = [ErrorCode::CanisterStopped, ErrorCode::CanisterStopping] - .contains(&user_error.code); + .contains(&user_error.error_code); if is_benign { continue; } else { @@ -1004,12 +981,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => { - panic!("Call to get_neurons_fund_audit_info failed: {:#?}", s) - } - }; Decode!(&result, GetNeuronsFundAuditInfoResponse).unwrap() } @@ -1056,16 +1027,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(reply) => reply, - WasmResult::Reject(reject) => { - panic!( - "get_network_economics_parameters was rejected by the NNS governance \ - canister: {:#?}", - reject - ) - } - }; Decode!(&result, NetworkEconomics).unwrap() } } @@ -1097,10 +1058,6 @@ pub mod nns { ) -> Result { let call_id = icrc1_transfer_request(pocket_ic, sender, transfer_arg).await; let result = pocket_ic.await_call(call_id).await.unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to icrc1_transfer failed: {:#?}", s), - }; Decode!(&result, Result).unwrap() } @@ -1117,10 +1074,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to account_balance failed: {:#?}", s), - }; Decode!(&result, Tokens).unwrap() } @@ -1156,7 +1109,7 @@ pub mod nns { // Assert result is ok. match result { - Ok(WasmResult::Reply(_reply)) => (), // Ok, + Ok(_reply) => (), // Ok, _ => panic!("{:?}", result), } } @@ -1185,12 +1138,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => { - panic!("Call to get_deployed_sns_by_proposal_id failed: {:#?}", s) - } - }; Decode!(&result, GetDeployedSnsByProposalIdResponse).unwrap() } @@ -1205,10 +1152,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_wasm failed: {:#?}", s), - }; Decode!(&result, GetWasmResponse) .unwrap() .wasm @@ -1231,12 +1174,6 @@ pub mod nns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => { - panic!("Call to get_latest_sns_version failed: {:#?}", s) - } - }; let response = Decode!(&result, ListUpgradeStepsResponse).unwrap(); let latest_version = response .steps @@ -1461,10 +1398,6 @@ pub mod sns { ) .await .expect("Error calling manage_neuron"); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to (SNS) manage_neuron failed: {:#?}", s), - }; Decode!(&result, sns_pb::ManageNeuronResponse).unwrap() } @@ -1538,7 +1471,7 @@ pub mod sns { Ok(proposal) => proposal, Err(user_error) => { if [ErrorCode::CanisterStopped, ErrorCode::CanisterStopping] - .contains(&user_error.code) + .contains(&user_error.error_code) { continue; } else { @@ -1573,7 +1506,7 @@ pub mod sns { canister_id: PrincipalId, proposal_id: sns_pb::ProposalId, sender: PrincipalId, - ) -> Result { + ) -> Result { pocket_ic .query_call( canister_id.into(), @@ -1585,17 +1518,7 @@ pub mod sns { .unwrap(), ) .await - .map(|result| match result { - WasmResult::Reply(reply) => { - Decode!(&reply, sns_pb::GetProposalResponse).unwrap() - } - WasmResult::Reject(reject) => { - panic!( - "get_proposal was rejected by the SNS governance canister: {:#?}", - reject - ) - } - }) + .map(|result| Decode!(&result, sns_pb::GetProposalResponse).unwrap()) } pub async fn list_neurons( @@ -1611,15 +1534,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(reply) => reply, - WasmResult::Reject(reject) => { - panic!( - "list_neurons was rejected by the SNS governance canister: {:#?}", - reject - ) - } - }; Decode!(&result, sns_pb::ListNeuronsResponse).unwrap() } @@ -1784,10 +1698,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_neuron failed: {:#?}", s), - }; let response = Decode!(&result, sns_pb::GetNeuronResponse).unwrap(); match response.result.expect("No result in response") { get_neuron_response::Result::Error(e) => Err(e), @@ -1915,10 +1825,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to ledger_id failed: {:#?}", s), - }; Decode!(&result, PrincipalId).unwrap() } @@ -1932,10 +1838,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to status failed: {:#?}", s), - }; Decode!(&result, Status).unwrap() } @@ -1961,10 +1863,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_blocks failed: {:#?}", s), - }; Decode!(&result, GetBlocksResponse).unwrap() } @@ -2021,10 +1919,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to icrc1_total_supply failed: {:#?}", s), - }; Decode!(&result, Nat).unwrap() } @@ -2042,10 +1936,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to icrc1_balance_of failed: {:#?}", s), - }; Decode!(&result, Nat).unwrap() } @@ -2075,10 +1965,6 @@ pub mod sns { let call_id = icrc1_transfer_request(pocket_ic, canister_id, sender, transfer_arg).await; let result = pocket_ic.await_call(call_id).await.unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to icrc1_transfer failed: {:#?}", s), - }; Decode!(&result, Result).unwrap() } @@ -2104,10 +1990,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_blocks failed: {:#?}", s), - }; Decode!(&result, GetBlocksResponse).unwrap() } @@ -2194,10 +2076,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to archives failed: {:#?}", s), - }; Decode!(&result, Vec).unwrap() } @@ -2216,10 +2094,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to icrc2_approve failed: {:#?}", s), - }; Decode!(&result, Result).unwrap() } @@ -2238,10 +2112,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to icrc2_allowance failed: {:#?}", s), - }; Decode!(&result, Allowance).unwrap() } @@ -2260,10 +2130,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to icrc2_transfer_from failed: {:#?}", s), - }; Decode!(&result, Result).unwrap() } } @@ -2295,10 +2161,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_blocks failed: {:#?}", s), - }; Decode!(&result, BlockRange).unwrap() } } @@ -2382,12 +2244,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => { - panic!("Call to get_sns_canisters_summary failed: {:#?}", s) - } - }; Decode!(&result, GetSnsCanistersSummaryResponse).unwrap() } } @@ -2512,10 +2368,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to new_sale_ticket failed: {:#?}", s), - }; Decode!(&result, GetInitResponse).unwrap() } @@ -2537,10 +2389,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to new_sale_ticket failed: {:#?}", s), - }; Decode!(&result, ListSnsNeuronRecipesResponse).unwrap() } @@ -2563,10 +2411,6 @@ pub mod sns { ) .await .map_err(|err| err.to_string())?; - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to new_sale_ticket failed: {:#?}", s), - }; Ok(Decode!(&result, NewSaleTicketResponse).unwrap()) } @@ -2589,10 +2433,6 @@ pub mod sns { ) .await .map_err(|err| err.to_string())?; - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to refresh_buyer_tokens failed: {:#?}", s), - }; Ok(Decode!(&result, RefreshBuyerTokensResponse).unwrap()) } @@ -2613,10 +2453,6 @@ pub mod sns { ) .await .map_err(|err| err.to_string())?; - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_buyer_state failed: {:#?}", s), - }; Ok(Decode!(&result, GetBuyerStateResponse).unwrap()) } @@ -2634,10 +2470,6 @@ pub mod sns { ) .await .map_err(|err| err.to_string())?; - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_open_ticket failed: {:#?}", s), - }; Ok(Decode!(&result, GetOpenTicketResponse).unwrap()) } @@ -2658,10 +2490,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to error_refund_icp failed: {:#?}", s), - }; Decode!(&result, ErrorRefundIcpResponse).unwrap() } @@ -2678,10 +2506,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_derived_state failed: {:#?}", s), - }; Decode!(&result, GetDerivedStateResponse).unwrap() } @@ -2698,10 +2522,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to get_lifecycle failed: {:#?}", s), - }; Decode!(&result, GetLifecycleResponse).unwrap() } @@ -2848,10 +2668,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => panic!("Call to finalize_swap failed: {:#?}", s), - }; Decode!(&result, FinalizeSwapResponse).unwrap() } @@ -2868,12 +2684,6 @@ pub mod sns { ) .await .unwrap(); - let result = match result { - WasmResult::Reply(result) => result, - WasmResult::Reject(s) => { - panic!("Call to get_auto_finalization_status failed: {:#?}", s) - } - }; Decode!(&result, GetAutoFinalizationStatusResponse).unwrap() } diff --git a/rs/pocket_ic_server/CHANGELOG.md b/rs/pocket_ic_server/CHANGELOG.md index c814a0a9a4f..44815080924 100644 --- a/rs/pocket_ic_server/CHANGELOG.md +++ b/rs/pocket_ic_server/CHANGELOG.md @@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Canisters created via `provisional_create_canister_with_cycles` with the management canister ID as the effective canister ID are created on an arbitrary subnet. +### Changed +- The response type `RawSubmitIngressResult` is replaced by `Result`. +- The response types `RawWasmResult` and `UserError` in `RawCanisterResult` are replaced by `Vec` and `RejectResponse`. + ### Removed - The endpoint `/instances//update/execute_ingress_message`: use the two endpoints `/instances//update/submit_ingress_message` and `/instances//update/await_ingress_message` diff --git a/rs/pocket_ic_server/src/pocket_ic.rs b/rs/pocket_ic_server/src/pocket_ic.rs index a74a6011c70..29505cc17c3 100644 --- a/rs/pocket_ic_server/src/pocket_ic.rs +++ b/rs/pocket_ic_server/src/pocket_ic.rs @@ -22,7 +22,6 @@ use ic_config::{ subnet_config::SubnetConfig, }; use ic_crypto_sha2::Sha256; -use ic_error_types::RejectCode; use ic_http_endpoints_public::{ call_v2, call_v3, metrics::HttpHandlerMetrics, CanisterReadStateServiceBuilder, IngressValidatorBuilder, QueryServiceBuilder, SubnetReadStateServiceBuilder, @@ -80,6 +79,7 @@ use pocket_ic::common::rest::{ RawCanisterCall, RawCanisterId, RawEffectivePrincipal, RawMessageId, RawSetStableMemory, SubnetInstructionConfig, SubnetKind, SubnetSpec, Topology, }; +use pocket_ic::{ErrorCode, RejectCode, RejectResponse}; use serde::{Deserialize, Serialize}; use slog::Level; use std::hash::Hash; @@ -122,6 +122,33 @@ pub(crate) type ApiResponse = BoxFuture<'static, (u16, BTreeMap> /// Used for generating canister ID ranges that do not appear on mainnet. pub const MAXIMUM_NUMBER_OF_SUBNETS_ON_MAINNET: u64 = 1024; +fn wasm_result_to_canister_result( + res: ic_state_machine_tests::WasmResult, + certified: bool, +) -> Result, RejectResponse> { + match res { + ic_state_machine_tests::WasmResult::Reply(data) => Ok(data), + ic_state_machine_tests::WasmResult::Reject(reject_message) => Err(RejectResponse { + reject_code: RejectCode::CanisterReject, + reject_message, + error_code: ErrorCode::CanisterRejectedMessage, + certified, + }), + } +} + +fn user_error_to_reject_response( + err: ic_error_types::UserError, + certified: bool, +) -> RejectResponse { + RejectResponse { + reject_code: RejectCode::try_from(err.reject_code() as u64).unwrap(), + reject_message: err.description().to_string(), + error_code: ErrorCode::try_from(err.code() as u64).unwrap(), + certified, + } +} + async fn into_api_response(resp: AxumResponse) -> (u16, BTreeMap>, Vec) { ( resp.status().into(), @@ -1388,7 +1415,7 @@ fn process_mock_canister_https_response( reject_codes.push(reject_code) } for reject_code in reject_codes { - if RejectCode::try_from(reject_code).is_err() { + if ic_error_types::RejectCode::try_from(reject_code).is_err() { return OpOut::Error(PocketIcError::InvalidRejectCode(reject_code)); } } @@ -1459,7 +1486,7 @@ fn process_mock_canister_https_response( } CanisterHttpResponse::CanisterHttpReject(reject) => { CanisterHttpResponseContent::Reject(CanisterHttpReject { - reject_code: RejectCode::try_from(reject.reject_code).unwrap(), + reject_code: ic_error_types::RejectCode::try_from(reject.reject_code).unwrap(), message: reject.message.clone(), }) } @@ -1581,7 +1608,7 @@ impl Operation for SubmitIngressMessage { } Err(SubmitIngressError::UserError(e)) => { eprintln!("Failed to submit ingress message: {:?}", e); - Err::(e).into() + OpOut::CanisterResult(Err(user_error_to_reject_response(e, false))) } Ok(msg_id) => OpOut::MessageId(( EffectivePrincipal::SubnetId(subnet.get_subnet_id()), @@ -1644,16 +1671,18 @@ impl Operation for AwaitIngressMessage { IngressStatus::Known { state: IngressState::Completed(result), .. - } => return Ok(result).into(), + } => { + return OpOut::CanisterResult(wasm_result_to_canister_result( + result, true, + )); + } IngressStatus::Known { state: IngressState::Failed(error), .. } => { - return Err::< - ic_state_machine_tests::WasmResult, - ic_state_machine_tests::UserError, - >(error) - .into() + return OpOut::CanisterResult(Err(user_error_to_reject_response( + error, true, + ))); } _ => {} } @@ -1700,11 +1729,11 @@ impl Operation for IngressMessageStatus { IngressStatus::Known { state: IngressState::Completed(result), .. - } => Ok(result).into(), + } => OpOut::CanisterResult(wasm_result_to_canister_result(result, true)), IngressStatus::Known { state: IngressState::Failed(error), .. - } => Err(error).into(), + } => OpOut::CanisterResult(Err(user_error_to_reject_response(error, true))), _ => OpOut::NoOutput, } } @@ -1731,15 +1760,20 @@ impl Operation for Query { match subnet { Ok(subnet) => { let delegation = pic.get_nns_delegation_for_subnet(subnet.get_subnet_id()); - subnet - .query_as_with_delegation( - self.0.sender, - self.0.canister_id, - self.0.method.clone(), - self.0.payload.clone(), - delegation, - ) - .into() + match subnet.query_as_with_delegation( + self.0.sender, + self.0.canister_id, + self.0.method.clone(), + self.0.payload.clone(), + delegation, + ) { + Ok(result) => { + OpOut::CanisterResult(wasm_result_to_canister_result(result, false)) + } + Err(user_error) => { + OpOut::CanisterResult(Err(user_error_to_reject_response(user_error, false))) + } + } } Err(e) => OpOut::Error(PocketIcError::BadIngressMessage(e)), } diff --git a/rs/pocket_ic_server/src/state_api/routes.rs b/rs/pocket_ic_server/src/state_api/routes.rs index 327a5ae7621..f08a5da8240 100644 --- a/rs/pocket_ic_server/src/state_api/routes.rs +++ b/rs/pocket_ic_server/src/state_api/routes.rs @@ -40,9 +40,9 @@ use pocket_ic::common::rest::{ HttpGatewayDetails, InstanceConfig, MockCanisterHttpResponse, RawAddCycles, RawCanisterCall, RawCanisterHttpRequest, RawCanisterId, RawCanisterResult, RawCycles, RawIngressStatusArgs, RawMessageId, RawMockCanisterHttpResponse, RawPrincipalId, RawSetStableMemory, RawStableMemory, - RawSubmitIngressResult, RawSubnetId, RawTime, RawWasmResult, Topology, + RawSubnetId, RawTime, Topology, }; -use pocket_ic::WasmResult; +use pocket_ic::RejectResponse; use serde::Serialize; use slog::Level; use std::str::FromStr; @@ -407,18 +407,7 @@ impl TryFrom for RawCanisterResult { type Error = OpConversionError; fn try_from(value: OpOut) -> Result { match value { - OpOut::CanisterResult(wasm_result) => { - let inner = match wasm_result { - Ok(WasmResult::Reply(wasm_result)) => { - RawCanisterResult::Ok(RawWasmResult::Reply(wasm_result)) - } - Ok(WasmResult::Reject(error_message)) => { - RawCanisterResult::Ok(RawWasmResult::Reject(error_message)) - } - Err(user_error) => RawCanisterResult::Err(user_error), - }; - Ok(inner) - } + OpOut::CanisterResult(result) => Ok(result.into()), _ => Err(OpConversionError), } } @@ -428,18 +417,7 @@ impl TryFrom for Option { type Error = OpConversionError; fn try_from(value: OpOut) -> Result { match value { - OpOut::CanisterResult(wasm_result) => { - let inner = match wasm_result { - Ok(WasmResult::Reply(wasm_result)) => { - Some(RawCanisterResult::Ok(RawWasmResult::Reply(wasm_result))) - } - Ok(WasmResult::Reject(error_message)) => { - Some(RawCanisterResult::Ok(RawWasmResult::Reject(error_message))) - } - Err(user_error) => Some(RawCanisterResult::Err(user_error)), - }; - Ok(inner) - } + OpOut::CanisterResult(result) => Ok(Some(result.into())), OpOut::NoOutput => Ok(None), _ => Err(OpConversionError), } @@ -492,17 +470,15 @@ impl TryFrom for Vec { } } -impl TryFrom for RawSubmitIngressResult { +impl TryFrom for Result { type Error = OpConversionError; fn try_from(value: OpOut) -> Result { match value { - OpOut::MessageId((effective_principal, message_id)) => { - Ok(RawSubmitIngressResult::Ok(RawMessageId { - effective_principal: effective_principal.into(), - message_id, - })) - } - OpOut::CanisterResult(Err(user_error)) => Ok(RawSubmitIngressResult::Err(user_error)), + OpOut::MessageId((effective_principal, message_id)) => Ok(Ok(RawMessageId { + effective_principal: effective_principal.into(), + message_id, + })), + OpOut::CanisterResult(Err(reject_response)) => Ok(Err(reject_response)), _ => Err(OpConversionError), } } @@ -834,7 +810,7 @@ async fn op_out_to_response(op_out: OpOut) -> Response { opout @ OpOut::MessageId(_) => ( StatusCode::OK, Json(ApiResponse::Success( - RawSubmitIngressResult::try_from(opout).unwrap(), + Result::::try_from(opout).unwrap(), )), ) .into_response(), @@ -976,7 +952,10 @@ pub async fn handler_submit_ingress_message( Path(instance_id): Path, headers: HeaderMap, extract::Json(raw_canister_call): extract::Json, -) -> (StatusCode, Json>) { +) -> ( + StatusCode, + Json>>, +) { let timeout = timeout_or_default(headers); match crate::pocket_ic::CanisterCall::try_from(raw_canister_call) { Ok(canister_call) => { diff --git a/rs/pocket_ic_server/src/state_api/state.rs b/rs/pocket_ic_server/src/state_api/state.rs index 05a0962c441..e13ed094bf0 100644 --- a/rs/pocket_ic_server/src/state_api/state.rs +++ b/rs/pocket_ic_server/src/state_api/state.rs @@ -38,7 +38,7 @@ use pocket_ic::common::rest::{ CanisterHttpRequest, HttpGatewayBackend, HttpGatewayConfig, HttpGatewayDetails, HttpGatewayInfo, Topology, }; -use pocket_ic::{ErrorCode, UserError, WasmResult}; +use pocket_ic::RejectResponse; use reqwest::Url; use serde::{Deserialize, Serialize}; use std::{ @@ -230,7 +230,7 @@ impl PocketIcApiStateBuilder { pub enum OpOut { NoOutput, Time(u64), - CanisterResult(Result), + CanisterResult(Result, RejectResponse>), CanisterId(CanisterId), Controllers(Vec), Cycles(u128), @@ -258,24 +258,6 @@ pub enum PocketIcError { Forbidden(String), } -impl From> for OpOut { - fn from( - r: Result, - ) -> Self { - let res = { - match r { - Ok(ic_state_machine_tests::WasmResult::Reply(wasm)) => Ok(WasmResult::Reply(wasm)), - Ok(ic_state_machine_tests::WasmResult::Reject(s)) => Ok(WasmResult::Reject(s)), - Err(user_err) => Err(UserError { - code: ErrorCode::try_from(user_err.code() as u64).unwrap(), - description: user_err.description().to_string(), - }), - } - }; - OpOut::CanisterResult(res) - } -} - impl std::fmt::Debug for OpOut { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/rs/pocket_ic_server/tests/test.rs b/rs/pocket_ic_server/tests/test.rs index 72f615d93df..68d9ad3eb06 100644 --- a/rs/pocket_ic_server/tests/test.rs +++ b/rs/pocket_ic_server/tests/test.rs @@ -16,7 +16,7 @@ use pocket_ic::common::rest::{ CreateHttpGatewayResponse, HttpGatewayBackend, HttpGatewayConfig, HttpGatewayDetails, HttpsConfig, InstanceConfig, SubnetConfigSet, SubnetKind, Topology, }; -use pocket_ic::{update_candid, PocketIc, PocketIcBuilder, WasmResult}; +use pocket_ic::{update_candid, PocketIc, PocketIcBuilder}; use rcgen::{CertificateParams, KeyPair}; use registry_canister::init::RegistryCanisterInitPayload; use reqwest::blocking::Client; @@ -628,12 +628,7 @@ fn check_counter(pic: &PocketIc, canister_id: Principal, expected_ctr: u32) { let res = pic .query_call(canister_id, Principal::anonymous(), "read", vec![]) .unwrap(); - match res { - WasmResult::Reply(data) => { - assert_eq!(u32::from_le_bytes(data.try_into().unwrap()), expected_ctr); - } - _ => panic!("Unexpected update call response"), - }; + assert_eq!(u32::from_le_bytes(res.try_into().unwrap()), expected_ctr); } /// Tests that the PocketIC topology and canister states diff --git a/rs/rosetta-api/icp/tests/integration_tests/tests/tests.rs b/rs/rosetta-api/icp/tests/integration_tests/tests/tests.rs index 9461ff50c0f..537b0f2c413 100644 --- a/rs/rosetta-api/icp/tests/integration_tests/tests/tests.rs +++ b/rs/rosetta-api/icp/tests/integration_tests/tests/tests.rs @@ -18,7 +18,6 @@ use icp_rosetta_integration_tests::{start_rosetta, RosettaContext}; use icrc_ledger_types::icrc1::account::Account; use icrc_ledger_types::icrc1::transfer::{BlockIndex, TransferArg, TransferError}; use num_traits::cast::ToPrimitive; -use pocket_ic::WasmResult; use pocket_ic::{nonblocking::PocketIc, PocketIcBuilder}; use rosetta_core::objects::ObjectMap; use serde::Deserialize; @@ -506,7 +505,7 @@ async fn test_rosetta_blocks_mode_enabled() { ); } -// a simple trait to simplify unwrapping and decoding a WasmResult +// a simple trait to simplify unwrapping and decoding Vec trait UnwrapCandid { fn unwrap(&self) -> &[u8]; fn unwrap_as Deserialize<'a>>(&self) -> T { @@ -514,12 +513,9 @@ trait UnwrapCandid { } } -impl UnwrapCandid for WasmResult { +impl UnwrapCandid for Vec { fn unwrap(&self) -> &[u8] { - match self { - WasmResult::Reply(bytes) => bytes, - WasmResult::Reject(err) => panic!("Cannot unwrap Reject: {err}"), - } + self.as_slice() } }