Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standardize errors #1165

Open
wants to merge 89 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
7260447
tmp
PolyProgrammist Apr 8, 2024
9ec4723
add persist_on_error
PolyProgrammist Apr 8, 2024
d5d5298
Merge branch 'master' into standartize-errors
PolyProgrammist May 3, 2024
f4a27e0
add another project, remove status, add approximate promise call
PolyProgrammist May 3, 2024
1243da8
simple project with simple test
PolyProgrammist May 6, 2024
5d166e7
add error method and call to it to the contract
PolyProgrammist May 6, 2024
56e5549
tmp
PolyProgrammist May 6, 2024
9c22faa
add state_write
PolyProgrammist May 6, 2024
b005c35
fix handle result and enable tests
PolyProgrammist May 7, 2024
53ef6fd
fix FunctionError::panic to panic_str and added a test that checks er…
PolyProgrammist May 7, 2024
3a2a41a
add json serialization automatically
PolyProgrammist May 7, 2024
1d4ec49
add struct to tests
PolyProgrammist May 7, 2024
b79ece8
use check_trait for standard errors instead of handle_result
PolyProgrammist May 7, 2024
8cfa0da
remove asref
PolyProgrammist May 7, 2024
1d2b594
tmp
PolyProgrammist May 7, 2024
c2b397a
made methods for promises generate by #[near]. pass error argument to…
PolyProgrammist May 7, 2024
47ad490
generate error function only if it has persist on error
PolyProgrammist May 13, 2024
0b37e7c
add error type name to output
PolyProgrammist May 13, 2024
eceb288
add abi for result OK
PolyProgrammist May 16, 2024
d35d1cf
refactor error-handling example, remove debug files
PolyProgrammist May 16, 2024
7ecede5
refactor: remove commented code, use HandlesResultExplicit and Handle…
PolyProgrammist May 16, 2024
4ee2aba
refactor errorwrapper
PolyProgrammist May 19, 2024
7a14556
fmt
PolyProgrammist May 20, 2024
48576b2
fix standartized -> standardized, return compilation tests back, fix …
PolyProgrammist May 20, 2024
966d400
refactor
PolyProgrammist May 20, 2024
7ec7ac4
refactor
PolyProgrammist May 20, 2024
9f96ec1
fix abi & fmt
PolyProgrammist May 20, 2024
52a426c
fix clippy and readme
PolyProgrammist May 20, 2024
8935303
ci: fix new 1.78 lints
May 7, 2024
aea83c7
test: `schemars` version update, `TRYBUILD=overwrite`
May 7, 2024
b427177
move functions above tests to fix clippy
PolyProgrammist May 20, 2024
74b54ea
fix compilation test
PolyProgrammist May 20, 2024
81c9559
refactor ResultTypeExt
PolyProgrammist May 20, 2024
8409206
fixed snapshot tests by moving contract_ser out of handle_error and s…
PolyProgrammist May 20, 2024
352a873
remove tests without handle_result
PolyProgrammist May 20, 2024
8167824
fix snap tests and add for new feature
PolyProgrammist May 20, 2024
26c2620
return 1 test
PolyProgrammist May 20, 2024
160651e
remove returns and add docs
PolyProgrammist May 22, 2024
d497ccd
Merge branch 'master' into standartize-errors
PolyProgrammist May 29, 2024
c646470
add cause
PolyProgrammist May 29, 2024
52e1041
add sdk contract error
PolyProgrammist May 29, 2024
adbffbc
add baseerror
PolyProgrammist May 29, 2024
080250e
add snapshot for generated method_error
PolyProgrammist May 29, 2024
745fe09
fmt
PolyProgrammist May 29, 2024
ca84208
fix clippy
PolyProgrammist May 29, 2024
8e3a59c
fix compilation tests
PolyProgrammist May 29, 2024
17df48c
add compilation failed test for incorrect result
PolyProgrammist May 29, 2024
64cf673
fix comp test
PolyProgrammist May 29, 2024
6e39ba3
tmp
PolyProgrammist May 29, 2024
2f837cc
add standard error and use it in one example, add inside_nearsdk for …
PolyProgrammist May 29, 2024
63ec033
add wrap_error and use it in PanicOnDefault
PolyProgrammist May 30, 2024
bd96f6d
add wrap to impl
PolyProgrammist May 30, 2024
2dfa359
move wrap outside standardized_error_panic_tokens
PolyProgrammist May 30, 2024
14e2b94
use string from instead of wrap
PolyProgrammist May 30, 2024
0ed933e
switch to new errors in examples for panic_str and require
PolyProgrammist May 30, 2024
67230f9
return err instead of panic
PolyProgrammist May 30, 2024
ed3e2c9
update some panic_strs in near-contract-standards
PolyProgrammist May 30, 2024
acee075
add unwrap_or_err, unwrap_or_return, fixed panic_str for near-contrac…
PolyProgrammist May 30, 2024
556e18d
introduce require_or_err
PolyProgrammist May 30, 2024
a82fc23
fix fungible token example
PolyProgrammist May 30, 2024
43a0360
fmt
PolyProgrammist May 30, 2024
43ac3ef
fix comilation tests
PolyProgrammist May 30, 2024
a49df2d
fix clippy ident : this creates an owned instance just for comparison
PolyProgrammist May 30, 2024
410d621
fix clippy & fungible
PolyProgrammist May 30, 2024
3564b20
fix clippy multiple things
PolyProgrammist May 30, 2024
93c47e2
fix fungible token test
PolyProgrammist May 30, 2024
c0f4a90
fix snapshots
PolyProgrammist May 31, 2024
47aec44
fix some problems in test core macos (comments)
PolyProgrammist May 31, 2024
9f2f4f0
flatten BaseError
PolyProgrammist May 31, 2024
7e5574f
fix snaps
PolyProgrammist May 31, 2024
89d8552
separate String::from implementation for BaseError
PolyProgrammist May 31, 2024
b0d0d5e
panic_str to panic_err in some cases
PolyProgrammist May 31, 2024
70e219b
panic_str to panic_err for serialization / deserialization, inconsist…
PolyProgrammist May 31, 2024
ee90f67
more panic_str to panic_err
PolyProgrammist May 31, 2024
b8e388b
all panic_str to panic_err
PolyProgrammist May 31, 2024
7b31261
fmt
PolyProgrammist May 31, 2024
f5e9db6
fix compilation test
PolyProgrammist May 31, 2024
9d2c9db
fix fungilbe token build
PolyProgrammist May 31, 2024
2e90e0f
fix clippy & invalid range expect tests
PolyProgrammist May 31, 2024
fb636c1
fix snaps
PolyProgrammist May 31, 2024
fe9fd69
renamings
PolyProgrammist May 31, 2024
6e4349a
AnyError to Error, try to avoid
PolyProgrammist May 31, 2024
b9a2282
divide sdk & custom errors
PolyProgrammist May 31, 2024
ba588e5
fmt
PolyProgrammist May 31, 2024
eb386b4
require to require_or_err in near-contract-standards and examples
PolyProgrammist May 31, 2024
6a0096a
fix tests
PolyProgrammist May 31, 2024
666eb9f
fix macos tests
PolyProgrammist May 31, 2024
873627c
add docs
PolyProgrammist May 31, 2024
65a23af
fix commented examples
PolyProgrammist May 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ want to disable default initialization, then you can prohibit it like this:
```rust
impl Default for StatusMessage {
fn default() -> Self {
near_sdk::env::panic_str("Contract should be initialized before the usage.")
near_sdk::env::panic_err(near_sdk::errors::ContractNotInitialized{}.into())
}
}
```
Expand Down Expand Up @@ -154,7 +154,7 @@ pub fn my_method(&mut self) {

pub fn my_method(&mut self ) {
if near_sdk::env::current_account_id() != near_sdk::env::predecessor_account_id() {
near_sdk::env::panic_str("Method my_method is private");
near_sdk::env::panic_err(near_sdk::errors::PrivateMethod::new("my_method"));
}
...
}
Expand Down
33 changes: 19 additions & 14 deletions examples/callback-results/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use near_sdk::require;
use near_sdk::{env, near, Promise, PromiseError};
use near_sdk::require_or_err;
use near_sdk::{env, near, BaseError, Promise, PromiseError};
use near_sdk::errors::{InvalidArgument, UnexpectedFailure, InvalidPromiseReturn};

const A_VALUE: u8 = 8;

Expand All @@ -25,24 +26,28 @@ impl Callback {

/// Returns a static string if fail is false, return
#[private]
pub fn b(fail: bool) -> &'static str {
pub fn b(fail: bool) -> Result<&'static str, BaseError> {
if fail {
env::panic_str("failed within function b");
return Err(UnexpectedFailure {
message: "Failed within function b".to_string(),
}
.into());
}
"Some string"
Ok("Some string")
}

/// Panics if value is 0, returns the value passed in otherwise.
#[private]
pub fn c(value: u8) -> u8 {
require!(value > 0, "Value must be positive");
value
pub fn c(value: u8) -> Result<u8, InvalidArgument> {
require_or_err!(value > 0, InvalidArgument::new("Value must be positive"));
Ok(value)
}

/// Panics if value is 0.
#[private]
pub fn d(value: u8) {
require!(value > 0, "Value must be positive");
pub fn d(value: u8) -> Result<(), InvalidArgument> {
require_or_err!(value > 0, InvalidArgument::new("Value must be positive"));
Ok(())
}

/// Receives the callbacks from the other promises called.
Expand All @@ -52,12 +57,12 @@ impl Callback {
#[callback_result] b: Result<String, PromiseError>,
#[callback_result] c: Result<u8, PromiseError>,
#[callback_result] d: Result<(), PromiseError>,
) -> (bool, bool, bool) {
require!(a == A_VALUE, "Promise returned incorrect value");
) -> Result<(bool, bool, bool), BaseError> {
require_or_err!(a == A_VALUE, InvalidPromiseReturn::new("Promise returned incorrect value"));
if let Ok(s) = b.as_ref() {
require!(s == "Some string");
require_or_err!(s == "Some string");
}
(b.is_err(), c.is_err(), d.is_err())
Ok((b.is_err(), c.is_err(), d.is_err()))
}
}

Expand Down
3 changes: 2 additions & 1 deletion examples/cross-contract-calls/low-level/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use near_sdk::serde_json;
use near_sdk::{env, near, require, Gas, NearToken, PromiseResult};
use near_sdk::errors::PromiseFailed;

// Prepaid gas for a single (not inclusive of recursion) `factorial` call.
const FACTORIAL_CALL_GAS: Gas = Gas::from_tgas(20);
Expand Down Expand Up @@ -45,7 +46,7 @@ impl CrossContract {
require!(env::promise_results_count() == 1);
let cur = match env::promise_result(0) {
PromiseResult::Successful(x) => serde_json::from_slice::<u32>(&x).unwrap(),
_ => env::panic_str("Promise with index 0 failed"),
_ => env::panic_err(PromiseFailed::new(Some(0), None).into()),
};
env::value_return(&serde_json::to_vec(&(cur * n)).unwrap());
}
Expand Down
5 changes: 5 additions & 0 deletions examples/error-handling/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[target.wasm32-unknown-unknown]
rustflags = ["-C", "link-arg=-s"]

[build]
target-dir = "../../target"
25 changes: 25 additions & 0 deletions examples/error-handling/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "error-handling"
version = "0.1.0"
authors = ["Near Inc <[email protected]>"]
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
near-sdk = { path = "../../near-sdk" }

[dev-dependencies]
near-workspaces = { version = "0.9.0", default-features = false, features = ["install"] }
test-case = "2.0"
tokio = { version = "1.14", features = ["full"] }
anyhow = "1.0"

[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
37 changes: 37 additions & 0 deletions examples/error-handling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# error-handling

cargo-near-new-project-description

## How to Build Locally?

Install [`cargo-near`](https://github.com/near/cargo-near) and run:

```bash
cargo near build
```

## How to Test Locally?

```bash
cargo test
```

## How to Deploy?

Deployment is automated with GitHub Actions CI/CD pipeline.
To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run:

```bash
cargo near deploy <account-id>
```

## Useful Links

- [cargo-near](https://github.com/near/cargo-near) - NEAR smart contract development toolkit for Rust
- [near CLI](https://near.cli.rs) - Interact with NEAR blockchain from command line
- [NEAR Rust SDK Documentation](https://docs.near.org/sdk/rust/introduction)
- [NEAR Documentation](https://docs.near.org)
- [NEAR StackOverflow](https://stackoverflow.com/questions/tagged/nearprotocol)
- [NEAR Discord](https://near.chat)
- [NEAR Telegram Developers Community Group](https://t.me/neardev)
- NEAR DevHub: [Telegram](https://t.me/neardevhub), [Twitter](https://twitter.com/neardevhub)
7 changes: 7 additions & 0 deletions examples/error-handling/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
TARGET="${CARGO_TARGET_DIR:-../../target}"
set -e
cd "$(dirname $0)"

cargo build --all --target wasm32-unknown-unknown --release
cp $TARGET/wasm32-unknown-unknown/release/error_handling.wasm ./res/
155 changes: 155 additions & 0 deletions examples/error-handling/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Find all our documentation at https://docs.near.org
use near_sdk::contract_error;
use near_sdk::near;
use near_sdk::BaseError;

#[contract_error]
pub enum MyErrorEnum {
X,
}

#[contract_error(sdk)]
pub struct MyErrorStruct {
x: u32,
}

#[near(contract_state)]
#[derive(Default)]
pub struct Contract {
value: u32,
}

#[near]
impl Contract {
#[init]
pub fn new() -> Self {
Self { value: 0 }
}

// Examples of RPC response for function call:
// is_error = false
// --- Result -------------------------
// 1
// ------------------------------------
// (changes value from 0 to 1)
//
// is_error = true
// Failed transaction
// Error:
// 0: Error: An error occurred during a `FunctionCall` Action, parameter is debug message.
// ExecutionError("Smart contract panicked: error in inc_handle_result")
// (does not change value)
#[handle_result]
pub fn inc_handle_result(&mut self, is_error: bool) -> Result<u32, &'static str> {
Comment on lines +42 to +43
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's provide doc strings that illustrate the potential return values from the RPC response point of view.

P.S. Apply the same reasoning below

self.value += 1;
if is_error {
Err("error in inc_handle_result")
} else {
Ok(self.value)
}
}

// Examples of RPC response for function call:
// is_error = false
// --- Result -------------------------
// 2
// ------------------------------------
// (changes value from 1 to 2)
//
// is_error = true
// Failed transaction
// Error:
// 0: Error: An error occurred during a `FunctionCall` Action, parameter is debug message.
// ExecutionError("Smart contract panicked: {\"error\":{\"error_type\":\"error_handling::MyErrorEnum\",\"value\":\"X\"}}")
// (changes value from 2 to 3)
#[persist_on_error]
pub fn inc_persist_on_err(&mut self, is_error: bool) -> Result<u32, MyErrorEnum> {
self.value += 1;
if is_error {
Err(MyErrorEnum::X)
} else {
Ok(self.value)
}
}

// Examples of RPC response for function call:
// is_error = false
// --- Result -------------------------
// 4
// ------------------------------------
// (changes value from 3 to 4)
//
// is_error = true
// Failed transaction
// Error:
// 0: Error: An error occurred during a `FunctionCall` Action, parameter is debug message.
// ExecutionError("Smart contract panicked: {\"error\":{\"error_type\":\"error_handling::MyErrorStruct\",\"value\":{\"x\":5}}}")
// (does not change value)
pub fn inc_just_result(&mut self, is_error: bool) -> Result<u32, MyErrorStruct> {
self.value += 1;
if is_error {
Err(MyErrorStruct { x: 5 })
} else {
Ok(self.value)
}
}

// Examples of RPC response for function call:
// is_error = false
// --- Result -------------------------
// 5
// ------------------------------------
// (changes value from 4 to 5)
//
// is_error = true
// Failed transaction
// Error:
// 0: Error: An error occurred during a `FunctionCall` Action, parameter is debug message.
// ExecutionError("Smart contract panicked: Error")
// (does not change value)
pub fn inc_just_simple(&mut self, is_error: bool) -> u32 {
self.value += 1;
if is_error {
::near_sdk::env::panic_str("Error");
} else {
self.value
}
}

// Examples of RPC response for function call:
// is_error = false
// --- Result -------------------------
// 6
// ------------------------------------
// (changes value from 5 to 6)
//
// is_error = true
// Failed transaction
// Error:
// 0: Error: An error occurred during a `FunctionCall` Action, parameter is debug message.
// ExecutionError("Smart contract panicked: {\\\"error\\\":{\\\"cause\\\":{\\\"info\\\":{\\\"error\\\":{\\\"x\\\":5}},\\\"name\\\":\\\"near_sdk::utils::contract_error::BaseError\\\"},\\\"name\\\":\\\"CUSTOM_CONTRACT_ERROR\\\"}}")
// (does not change value)
pub fn inc_base_error(&mut self, is_error: bool) -> Result<u32, BaseError> {
self.value += 1;
if is_error {
Err(MyErrorStruct { x: 5 }.into())
} else {
Ok(self.value)
}
}

// Does not compile as u64 is not marked with contract_error
// > the trait `ContractErrorTrait` is not implemented for `u64`
// pub fn inc_incorrect_result_type(&mut self, is_error: bool) -> Result<u32, u64> {
// self.value += 1;
// if is_error {
// Err(0)
// } else {
// Ok(self.value)
// }
// }

pub fn get_value(&self) -> u32 {
self.value
}
}
59 changes: 59 additions & 0 deletions examples/error-handling/tests/test_basics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use near_sdk::serde_json;
use near_workspaces::Contract;
use serde_json::json;

async fn get_value(contract: &Contract) -> anyhow::Result<u64> {
let get_value: serde_json::Value =
contract.call("get_value").args_json(json!({})).view().await?.json()?;

println!("get_value: {:?}", get_value);

get_value.as_u64().ok_or_else(|| anyhow::anyhow!("get_value is not a u64"))
}

async fn check_call(
contract: &Contract,
method: &str,
is_error: bool,
expected_value: u64,
expected_error: Option<String>,
) {
let res = contract
.call(method)
.args_json(json!({ "is_error": is_error }))
.max_gas()
.transact()
.await
.unwrap();
if is_error {
assert!(res.is_failure());
if let Some(expected_error) = expected_error {
let string_error =
format!("{:?}", res.failures()[0].clone().into_result().unwrap_err());
assert_eq!(string_error, expected_error);
}
} else {
assert!(res.is_success());
}
assert_eq!(get_value(&contract).await.unwrap(), expected_value);
}

#[tokio::test]
async fn test_error_handling() -> anyhow::Result<()> {
let worker = near_workspaces::sandbox().await?;
let contract =
worker.dev_deploy(&std::fs::read(format!("res/{}.wasm", "error_handling"))?).await?;

check_call(&contract, "inc_handle_result", false, 1, None).await;
check_call(&contract, "inc_persist_on_err", false, 2, None).await;
check_call(&contract, "inc_just_result", false, 3, None).await;
check_call(&contract, "inc_just_simple", false, 4, None).await;
check_call(&contract, "inc_base_error", false, 5, None).await;
check_call(&contract, "inc_handle_result", true, 5, None).await;
check_call(&contract, "inc_persist_on_err", true, 6, Some("Error { repr: Custom { kind: Execution, error: ActionError(ActionError { index: Some(0), kind: FunctionCallError(ExecutionError(\"Smart contract panicked: {\\\"error\\\":{\\\"cause\\\":{\\\"info\\\":\\\"X\\\",\\\"name\\\":\\\"error_handling::MyErrorEnum\\\"},\\\"name\\\":\\\"CUSTOM_CONTRACT_ERROR\\\"}}\")) }) } }".to_string())).await;
check_call(&contract, "inc_just_result", true, 6, Some("Error { repr: Custom { kind: Execution, error: ActionError(ActionError { index: Some(0), kind: FunctionCallError(ExecutionError(\"Smart contract panicked: {\\\"error\\\":{\\\"cause\\\":{\\\"info\\\":{\\\"x\\\":5},\\\"name\\\":\\\"error_handling::MyErrorStruct\\\"},\\\"name\\\":\\\"SDK_CONTRACT_ERROR\\\"}}\")) }) } }".to_string())).await;
check_call(&contract, "inc_base_error", true, 6, Some("Error { repr: Custom { kind: Execution, error: ActionError(ActionError { index: Some(0), kind: FunctionCallError(ExecutionError(\"Smart contract panicked: {\\\"error\\\":{\\\"cause\\\":{\\\"info\\\":{\\\"error\\\":{\\\"x\\\":5}},\\\"name\\\":\\\"near_sdk::utils::contract_error::BaseError\\\"},\\\"name\\\":\\\"CUSTOM_CONTRACT_ERROR\\\"}}\")) }) } }".to_string())).await;
check_call(&contract, "inc_just_simple", true, 6, None).await;

Ok(())
}
Loading
Loading