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 24 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
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) - Iteract with NEAR blockchain from command line

Check warning on line 31 in examples/error-handling/README.md

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"Iteract" should be "Interact".
- [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/
66 changes: 66 additions & 0 deletions examples/error-handling/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Find all our documentation at https://docs.near.org
use near_sdk::borsh::{BorshDeserialize, BorshSerialize};
use near_sdk::contract_error;
use near_sdk::near;
use near_sdk::FunctionError;

#[contract_error]
enum MyErrorEnum {
X,
}

#[contract_error]
struct MyErrorStruct {
x: u32,
}

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

#[near]
impl Contract {
#[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 {
return Err("error in inc_handle_result");
} else {
return Ok(self.value);
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 assume that readers of the examples are familiar with the fact that the last statement in Rust block is the return value, and don't explicitly use return statements:

Suggested change
return Err("error in inc_handle_result");
} else {
return Ok(self.value);
Err("error in inc_handle_result")
} else {
Ok(self.value)

P.S. Apply it in all other occurences

}
}

#[persist_on_error]
pub fn inc_persist_on_err(&mut self, is_error: bool) -> Result<u32, MyErrorEnum> {
self.value += 1;
if is_error {
return Err(MyErrorEnum::X);
} else {
return Ok(self.value);
}
}

pub fn inc_just_result(&mut self, is_error: bool) -> Result<u32, MyErrorStruct> {
self.value += 1;
if is_error {
return Err(MyErrorStruct { x: 5 });
} else {
return Ok(self.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 {
return self.value;
}
}

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

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_factorial() -> 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_handle_result", true, 4, None).await;
check_call(&contract, "inc_persist_on_err", true, 5, Some("Error { repr: Custom { kind: Execution, error: ActionError(ActionError { index: Some(0), kind: FunctionCallError(ExecutionError(\"Smart contract panicked: {\\\"error\\\":{\\\"error_type\\\":\\\"error_handling::MyErrorEnum\\\",\\\"value\\\":\\\"X\\\"}}\")) }) } }".to_string())).await;
check_call(&contract, "inc_just_result", true, 5, Some("Error { repr: Custom { kind: Execution, error: ActionError(ActionError { index: Some(0), kind: FunctionCallError(ExecutionError(\"Smart contract panicked: {\\\"error\\\":{\\\"error_type\\\":\\\"error_handling::MyErrorStruct\\\",\\\"value\\\":{\\\"x\\\":5}}}\")) }) } }".to_string())).await;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I talked with some engineers on nearcore side and it seems that there is no capacity on their side to address this error handling on their side :-(

We will probably have to contribute it ourselves. I will guide you there once we will get to it.

check_call(&contract, "inc_just_simple", true, 5, None).await;

Ok(())
}
6 changes: 5 additions & 1 deletion near-sdk-macros/src/core_impl/abi/abi_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,15 @@ impl ImplItemMethodInfo {
match &self.attr_signature_info.returns.kind {
Default => quote! { ::std::option::Option::None },
General(ty) => self.abi_result_tokens_with_return_value(ty),
HandlesResult(ty) => {
HandlesResultExplicit(ty) => {
// extract the `Ok` type from the result
let ty = parse_quote! { <#ty as near_sdk::__private::ResultTypeExt>::Okay };
self.abi_result_tokens_with_return_value(&ty)
}
HandlesResultImplicit(ty) => {
let ty = parse_quote! { <#ty as near_sdk::__private::ResultTypeExt>::Okay };
self.abi_result_tokens_with_return_value(&ty)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::core_impl::info_extractor::{ImplItemMethodInfo, SerializerType};
use crate::core_impl::utils;
use crate::core_impl::{MethodKind, ReturnKind};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use serde::de::value;
use syn::token::Token;
use syn::Receiver;

impl ImplItemMethodInfo {
Expand All @@ -28,7 +31,8 @@ impl ImplItemMethodInfo {
// here.
ReturnKind::Default => self.void_return_body_tokens(),
ReturnKind::General(_) => self.value_return_body_tokens(),
ReturnKind::HandlesResult { .. } => self.result_return_body_tokens(),
ReturnKind::HandlesResultExplicit { .. } => self.result_return_body_tokens(),
ReturnKind::HandlesResultImplicit { .. } => self.result_return_body_tokens(),
};

quote! {
Expand Down Expand Up @@ -84,6 +88,7 @@ impl ImplItemMethodInfo {
let value_ser = self.value_ser_tokens();
let value_return = self.value_return_tokens();
let result_identifier = self.result_identifier();
let handle_error = self.error_handling_tokens();

quote! {
#contract_init
Expand All @@ -94,11 +99,39 @@ impl ImplItemMethodInfo {
#value_return
#contract_ser
}
::std::result::Result::Err(err) => ::near_sdk::FunctionError::panic(&err)
::std::result::Result::Err(err) => {
#contract_ser
#handle_error
}
}
}
}

fn error_handling_tokens(&self) -> TokenStream2 {
if let ReturnKind::HandlesResultImplicit(status_result) =
&self.attr_signature_info.returns.kind
{
if status_result.persist_on_error {
let error_method_name =
quote::format_ident!("{}_error", self.attr_signature_info.ident);
quote! {
let promise = Contract::ext(::near_sdk::env::current_account_id()).#error_method_name(err).as_return();
}
} else {
let error_type = utils::get_error_type_from_status(status_result);
utils::standardized_error_panic_tokens(&error_type)
}
} else if let ReturnKind::HandlesResultExplicit { .. } =
&self.attr_signature_info.returns.kind
{
quote! {
::near_sdk::FunctionError::panic(&err)
}
} else {
quote! {}
}
}

fn panic_hook_tokens(&self) -> TokenStream2 {
quote! {
::near_sdk::env::setup_panic_hook();
Expand Down
31 changes: 30 additions & 1 deletion near-sdk-macros/src/core_impl/code_generator/item_impl_info.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
use crate::core_impl::ext::generate_ext_function_wrappers;
use crate::core_impl::utils;
use crate::core_impl::ReturnKind;
use crate::ItemImplInfo;
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use quote::{format_ident, quote, ToTokens};
use syn::{spanned::Spanned, Ident};

impl ItemImplInfo {
/// Generate the code that wraps
pub fn wrapper_code(&self) -> TokenStream2 {
let mut res = TokenStream2::new();
let mut checks = quote! {};
for method in &self.methods {
res.extend(method.method_wrapper());
match method.attr_signature_info.returns.kind {
ReturnKind::HandlesResultImplicit { .. } => {
let error_type = match &method.attr_signature_info.returns.original {
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ty) => {
let x = utils::extract_error_type(&ty);
quote! { #x }
}
};
let method_name = &method.attr_signature_info.ident;
let concatenated = format_ident!("assert_implements_my_trait{}", method_name);

checks.extend(quote! {
fn #concatenated() {
let _ = near_sdk::check_trait as fn(&#error_type);
}
});
}
_ => {}
}
}
let current_type = &self.ty;
res.extend(quote! {
impl #current_type {
#checks
}
});
res
}

Expand Down
2 changes: 2 additions & 0 deletions near-sdk-macros/src/core_impl/code_generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod item_trait_info;

mod item_impl_info;

mod error_generation;

pub(crate) mod ext;
pub(crate) mod metadata;

Expand Down
2 changes: 1 addition & 1 deletion near-sdk-macros/src/core_impl/code_generator/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn generate_serializer(
let struct_decl = attr_sig_info.input_struct_ser();
let constructor_call = attr_sig_info.constructor_expr_ref();
let constructor = quote! { let __args = #constructor_call; };
let value_ser = match serializer {
let value_ser: TokenStream2 = match serializer {
SerializerType::JSON => quote! {
::near_sdk::serde_json::to_vec(&__args).expect("Failed to serialize the cross contract args using JSON.")
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ impl AttrSigInfo {
}
visitor.visit_result_serializer_attr(attr, &serializer)?;
}
"persist_on_error" => {
visitor.visit_persist_on_error_attr(attr)?;
}
"handle_result" => {
if let Some(value) = args.aliased {
let handle_result = HandleResultAttr { check: value };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ mod tests {
}

#[test]
fn init_result_without_handle_result() {
fn init_result_without_w() {
let impl_type: Type = syn::parse_str("Hello").unwrap();
let mut method: ImplItemMethod = parse_quote! {
#[init]
Expand Down
9 changes: 8 additions & 1 deletion near-sdk-macros/src/core_impl/info_extractor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,12 @@ pub struct Returns {
pub enum ReturnKind {
Default,
General(Type),
HandlesResult(Type),
HandlesResultExplicit(Type),
HandlesResultImplicit(StatusResult),
}

#[derive(Clone, PartialEq, Eq)]
pub struct StatusResult {
pub result_type: Type,
pub persist_on_error: bool,
}
Loading
Loading