Skip to content

Commit

Permalink
Merge pull request #1299 from CosmWasm/implement-abort
Browse files Browse the repository at this point in the history
Implement panic handler
  • Loading branch information
webmaster128 authored May 11, 2022
2 parents d80892e + 2eea236 commit e468d58
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 8 deletions.
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,15 @@ jobs:
- run:
name: Build library for native target (all features)
working_directory: ~/project/packages/std
command: cargo build --locked --features iterator,staking,stargate
command: cargo build --locked --features abort,iterator,staking,stargate
- run:
name: Build library for wasm target (all features)
working_directory: ~/project/packages/std
command: cargo wasm --locked --features iterator,staking,stargate
command: cargo wasm --locked --features abort,iterator,staking,stargate
- run:
name: Run unit tests (all features)
working_directory: ~/project/packages/std
command: cargo test --locked --features iterator,staking,stargate
command: cargo test --locked --features abort,iterator,staking,stargate
- run:
name: Build and run schema generator
working_directory: ~/project/packages/std
Expand Down Expand Up @@ -945,7 +945,7 @@ jobs:
- run:
name: Clippy linting on std (all feature flags)
working_directory: ~/project/packages/std
command: cargo clippy --all-targets --features iterator,staking,stargate -- -D warnings
command: cargo clippy --all-targets --features abort,iterator,staking,stargate -- -D warnings
- run:
name: Clippy linting on storage (no feature flags)
working_directory: ~/project/packages/storage
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ and this project adheres to

## [Unreleased]

### Added

- cosmwasm-std: When the new `abort` feature is enabled, cosmwasm-std installs a
panic handler that aborts the contract and passes the panic message to the
host. The `abort` feature can only be used when deploying to chains that
implement the import. For this reason, it's not yet enabled by default.
([#1299])
- cosmwasm-vm: A new import `abort` is created to abort contract execution when
requested by the contract. ([#1299])

[#1299]: https://github.com/CosmWasm/cosmwasm/pull/1299

## [1.0.0-rc.0] - 2022-05-05

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion contracts/hackatom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ cranelift = ["cosmwasm-vm/cranelift"]
backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"]

[dependencies]
cosmwasm-std = { path = "../../packages/std", default-features = false }
cosmwasm-std = { path = "../../packages/std", default-features = false, features = ["abort"] }
rust-argon2 = "0.8"
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
Expand Down
15 changes: 15 additions & 0 deletions contracts/hackatom/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,22 @@ fn do_allocate_large_memory(pages: u32) -> Result<Response, HackError> {
}

fn do_panic() -> Result<Response, HackError> {
// Uncomment your favourite panic case

// panicked at 'This page intentionally faulted', src/contract.rs:53:5
panic!("This page intentionally faulted");

// panicked at 'oh no (a = 3)', src/contract.rs:56:5
// let a = 3;
// panic!("oh no (a = {a})");

// panicked at 'attempt to subtract with overflow', src/contract.rs:59:13
// #[allow(arithmetic_overflow)]
// let _ = 5u32 - 8u32;

// panicked at 'no entry found for key', src/contract.rs:62:13
// let map = std::collections::HashMap::<String, String>::new();
// let _ = map["foo"];
}

fn do_user_errors_in_api_calls(api: &dyn Api) -> Result<Response, HackError> {
Expand Down
6 changes: 5 additions & 1 deletion contracts/hackatom/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,11 @@ fn execute_panic() {
);
match execute_res.unwrap_err() {
VmError::RuntimeErr { msg, .. } => {
assert_eq!(msg, "Wasmer runtime error: RuntimeError: unreachable")
assert!(
msg.contains("Aborted: panicked at 'This page intentionally faulted'"),
"Must contain panic message"
);
assert!(msg.contains("contract.rs:"), "Must contain file and line");
}
err => panic!("Unexpected error: {:?}", err),
}
Expand Down
100 changes: 100 additions & 0 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Features

Features are a mechanism to negotiate functionality between a contract and an
environment (i.e. the chain that embeds cosmwasm-vm/[wasmvm]) in a very
primitive way. The contract defines required features. The environment defines
supported features. If the required features are all supported, the contract can
be used. Doing this check when the contract is first stored ensures missing
features are detected early and not when a user tries to execute a certain code
path.

## Disambiguation

This document is about app level features in the CosmWasm VM. Features should
not be confused with Cargo's build system features, even when connected.
Features can be implemented in any language that compiles to Wasm.

## Required features

The contract defines required features using a marker export function that takes
no arguments and returns no value. The name of the export needs to start with
"requires\_" followed by the name of the feature. Do yourself a favor and keep
the name all lower ASCII alphanumerical.

An example of such markers in cosmwasm-std are those:

```rust
#[cfg(feature = "iterator")]
#[no_mangle]
extern "C" fn requires_iterator() -> () {}

#[cfg(feature = "staking")]
#[no_mangle]
extern "C" fn requires_staking() -> () {}

#[cfg(feature = "stargate")]
#[no_mangle]
extern "C" fn requires_stargate() -> () {}
```

which in Wasm compile to this:

```
# ...
(export "requires_staking" (func 181))
(export "requires_stargate" (func 181))
(export "requires_iterator" (func 181))
# ...
(func (;181;) (type 12)
nop)
# ...
(type (;12;) (func))
```

As mentioned above, the Cargo features are independent of the features we talk
about and it is perfectly fine to have a requires\_\* export that is
unconditional in a library or a contract.

The marker export functions can be executed, but the VM does not require such a
call to succeed. So a contract can use no-op implementation or crashing
implementation.

## Supported features

An instance of the main `Cache` has `supported_features` in its `CacheOptions`.
This value is set in the caller, such as
[here](https://github.com/CosmWasm/wasmvm/blob/v1.0.0-rc.0/libwasmvm/src/cache.rs#L75)
and
[here](https://github.com/CosmWasm/wasmvm/blob/v1.0.0-rc.0/libwasmvm/src/cache.rs#L62).
`features_from_csv` takes a comma separated list and returns a set of features.
This features list is set
[in keeper.go](https://github.com/CosmWasm/wasmd/blob/v0.27.0-rc0/x/wasm/keeper/keeper.go#L100)
and
[in app.go](https://github.com/CosmWasm/wasmd/blob/v0.27.0-rc0/app/app.go#L475-L496).

## Common features

Here is a list of features created in the past. Since features can be created
between contract and environment, we don't know them all in the VM.

- `iterator` is for storage backends that allow range queries. Not all types of
databases do that. There are trees that don't allow it and Secret Network does
not support iterators for other technical reasons.
- `stargate` is for messages and queries that came with the Cosmos SDK upgrade
"Stargate". It primarily includes protobuf messages and IBC support.
- `staking` is for chains with the Cosmos SDK staking module. There are Cosmos
chains that don't use this (e.g. Tgrade).

## What's a good feature?

A good feature makes sense to be disabled. The examples above explain why the
feature is not present in some environments.

When functionality is always present in the VM (such as a new import implemented
directly in the VM, see [#1299]), we should not use features. They just create
fragmentation in the CosmWasm ecosystem and increase the barrier for adoption.
Instead the `check_wasm_imports` check is used to validate this when the
contract is stored.

[wasmvm]: https://github.com/CosmWasm/wasmvm
[#1299]: https://github.com/CosmWasm/cosmwasm/pull/1299
1 change: 1 addition & 0 deletions packages/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ features = ["stargate", "staking"]

[features]
default = ["iterator"]
abort = []
# iterator allows us to iterate over all DB items in a given range
# optional as some merkle stores (like tries) don't support this
# given Ethereum 1.0, 2.0, Substrate, and other major projects use Tries
Expand Down
26 changes: 26 additions & 0 deletions packages/std/src/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use crate::ibc::{
};
use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage};
use crate::memory::{alloc, consume_region, release_buffer, Region};
#[cfg(feature = "abort")]
use crate::panic::install_panic_handler;
use crate::query::CustomQuery;
use crate::results::{ContractResult, QueryResponse, Reply, Response};
use crate::serde::{from_slice, to_vec};
Expand Down Expand Up @@ -93,6 +95,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_instantiate(
instantiate_fn,
env_ptr as *mut Region,
Expand Down Expand Up @@ -121,6 +125,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_execute(
execute_fn,
env_ptr as *mut Region,
Expand Down Expand Up @@ -148,6 +154,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_migrate(migrate_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -170,6 +178,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_sudo(sudo_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -191,6 +201,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_reply(reply_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -211,6 +223,8 @@ where
M: DeserializeOwned,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_query(query_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -232,6 +246,8 @@ where
Q: CustomQuery,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_ibc_channel_open(contract_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -255,6 +271,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_ibc_channel_connect(contract_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -278,6 +296,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_ibc_channel_close(contract_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -302,6 +322,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_ibc_packet_receive(contract_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -326,6 +348,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_ibc_packet_ack(contract_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand All @@ -351,6 +375,8 @@ where
C: CustomMsg,
E: ToString,
{
#[cfg(feature = "abort")]
install_panic_handler();
let res = _do_ibc_packet_timeout(contract_fn, env_ptr as *mut Region, msg_ptr as *mut Region);
let v = to_vec(&res).unwrap();
release_buffer(v) as u32
Expand Down
11 changes: 11 additions & 0 deletions packages/std/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const HUMAN_ADDRESS_BUFFER_LENGTH: usize = 90;
// A complete documentation those functions is available in the VM that provides them:
// https://github.com/CosmWasm/cosmwasm/blob/v1.0.0-beta/packages/vm/src/instance.rs#L89-L206
extern "C" {
#[cfg(feature = "abort")]
fn abort(source_ptr: u32);

fn db_read(key: u32) -> u32;
fn db_write(key: u32, value: u32);
fn db_remove(key: u32);
Expand Down Expand Up @@ -395,3 +398,11 @@ impl Querier for ExternalQuerier {
})
}
}

#[cfg(feature = "abort")]
pub fn handle_panic(message: &str) {
// keep the boxes in scope, so we free it at the end (don't cast to pointers same line as build_region)
let region = build_region(message.as_bytes());
let region_ptr = region.as_ref() as *const Region as u32;
unsafe { abort(region_ptr) };
}
1 change: 1 addition & 0 deletions packages/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod import_helpers;
#[cfg(feature = "iterator")]
mod iterator;
mod math;
mod panic;
mod query;
mod results;
mod sections;
Expand Down
14 changes: 14 additions & 0 deletions packages/std/src/panic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Installs a panic handler that aborts the contract execution
/// and sends the panic message and location to the host.
///
/// This overrides any previous panic handler. See <https://doc.rust-lang.org/std/panic/fn.set_hook.html>
/// for details.
#[cfg(all(feature = "abort", target_arch = "wasm32"))]
pub fn install_panic_handler() {
use super::imports::handle_panic;
std::panic::set_hook(Box::new(|info| {
// E.g. "panicked at 'oh no (a = 3)', src/contract.rs:51:5"
let full_message = info.to_string();
handle_panic(&full_message);
}));
}
4 changes: 3 additions & 1 deletion packages/vm/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,11 @@ mod tests {
}

fn make_stargate_testing_options() -> CacheOptions {
let mut feature = default_features();
feature.insert("stargate".into());
CacheOptions {
base_dir: TempDir::new().unwrap().into_path(),
supported_features: features_from_csv("iterator,staking,stargate"),
supported_features: feature,
memory_cache_size: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit: TESTING_MEMORY_LIMIT,
}
Expand Down
1 change: 1 addition & 0 deletions packages/vm/src/compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::static_analysis::{deserialize_wasm, ExportInfo};
/// Lists all imports we provide upon instantiating the instance in Instance::from_module()
/// This should be updated when new imports are added
const SUPPORTED_IMPORTS: &[&str] = &[
"env.abort",
"env.db_read",
"env.db_write",
"env.db_remove",
Expand Down
Loading

0 comments on commit e468d58

Please sign in to comment.