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

Implement panic handler #1299

Merged
merged 13 commits into from
May 11, 2022
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
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
19 changes: 18 additions & 1 deletion contracts/hackatom/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub fn instantiate(
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, HackError> {
deps.api.debug("here we go 🚀");
// deps.api.debug("here we go 🚀");

deps.storage.set(
CONFIG_KEY,
Expand All @@ -31,6 +31,23 @@ pub fn instantiate(
})?,
);

// Uncomment your favourite panic case

// panicked at 'Now what?', src/contract.rs:53:5
panic!("Now what?");

// 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"];

// This adds some unrelated event attribute for testing purposes
Ok(Response::new().add_attribute("Let the", "hacking begin"))
}
Expand Down
3 changes: 2 additions & 1 deletion contracts/hackatom/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ fn make_init_msg() -> (InstantiateMsg, String) {
#[test]
fn proper_initialization() {
let mut deps = mock_instance(WASM, &[]);
assert_eq!(deps.required_features().len(), 0);
assert_eq!(deps.required_features().len(), 1);
assert!(deps.required_features().contains("abort"));

let verifier = String::from("verifies");
let beneficiary = String::from("benefits");
Expand Down
6 changes: 3 additions & 3 deletions devtools/check_workspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ cargo fmt
(
cd packages/std
cargo check
cargo check --features iterator,staking,stargate
cargo check --features abort,iterator,staking,stargate
cargo wasm-debug
cargo wasm-debug --features iterator,staking,stargate
cargo clippy --all-targets --features iterator,staking,stargate -- -D warnings
cargo wasm-debug --features abort,iterator,staking,stargate
cargo clippy --all-targets --features abort,iterator,staking,stargate -- -D warnings
cargo schema
)
(cd packages/storage && cargo build && cargo clippy --all-targets --features iterator -- -D warnings)
Expand Down
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
30 changes: 30 additions & 0 deletions packages/std/src/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ 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};
use crate::types::Env;
use crate::{CustomMsg, Deps, DepsMut, MessageInfo};

#[cfg(feature = "abort")]
Copy link
Member

Choose a reason for hiding this comment

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

We don't need this at all.

This is a request from the Go/Wasmd runtime that it provides a feature, something which is passed through between the Wasm contract and the Go runtime.

This is not the case here

#[no_mangle]
extern "C" fn requires_abort() -> () {}

#[cfg(feature = "iterator")]
#[no_mangle]
extern "C" fn requires_iterator() -> () {}
Expand Down Expand Up @@ -93,6 +99,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 +129,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 +158,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 +182,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 +205,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 +227,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 +250,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 +275,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 +300,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 +326,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 +352,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 +379,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")]
webmaster128 marked this conversation as resolved.
Show resolved Hide resolved
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
18 changes: 18 additions & 0 deletions packages/std/src/panic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// When compiled to Wasm, this installs a panic handler that aborts the
/// contract execution and sends the panic message and location to the host.
/// For other targets, this is a noop.
///
/// This overrides any previous panic handler. See <https://doc.rust-lang.org/std/panic/fn.set_hook.html>
/// for details.
#[cfg(feature = "abort")]
pub fn install_panic_handler() {
#[cfg(target_arch = "wasm32")]
{
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);
}));
}
}
2 changes: 1 addition & 1 deletion packages/vm/examples/check_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use clap::{App, Arg};
use cosmwasm_vm::features_from_csv;
use cosmwasm_vm::internals::{check_wasm, compile};

const DEFAULT_SUPPORTED_FEATURES: &str = "iterator,staking,stargate";
const DEFAULT_SUPPORTED_FEATURES: &str = "abort,iterator,staking,stargate";

pub fn main() {
let matches = App::new("Contract checking")
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/examples/multi_threaded_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const THREADS: usize = SAVE_WASM_THREADS + INSTANTIATION_THREADS;
pub fn main() {
let options = CacheOptions {
base_dir: TempDir::new().unwrap().into_path(),
supported_features: features_from_csv("iterator,staking"),
supported_features: features_from_csv("abort,iterator,staking"),
memory_cache_size: MEMORY_CACHE_SIZE,
instance_memory_limit: DEFAULT_MEMORY_LIMIT,
};
Expand Down
6 changes: 4 additions & 2 deletions packages/vm/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ mod tests {
static IBC_CONTRACT: &[u8] = include_bytes!("../testdata/ibc_reflect.wasm");

fn default_features() -> HashSet<String> {
features_from_csv("iterator,staking")
features_from_csv("abort,iterator,staking")
webmaster128 marked this conversation as resolved.
Show resolved Hide resolved
}

fn make_testing_options() -> CacheOptions {
Expand All @@ -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
14 changes: 14 additions & 0 deletions packages/vm/src/errors/vm_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ use crate::backend::BackendError;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum VmError {
#[error("Aborted: {}", msg)]
Aborted {
msg: String,
#[cfg(feature = "backtraces")]
backtrace: Backtrace,
},
#[error("Error calling into the VM's backend: {}", source)]
BackendErr {
source: BackendError,
Expand Down Expand Up @@ -142,6 +148,14 @@ pub enum VmError {
}

impl VmError {
pub(crate) fn aborted(msg: impl Into<String>) -> Self {
VmError::Aborted {
msg: msg.into(),
#[cfg(feature = "backtraces")]
backtrace: Backtrace::capture(),
}
}

pub(crate) fn backend_err(original: BackendError) -> Self {
VmError::BackendErr {
source: original,
Expand Down
13 changes: 13 additions & 0 deletions packages/vm/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ const MAX_COUNT_ED25519_BATCH: usize = 256;
/// Max length for a debug message
const MAX_LENGTH_DEBUG: usize = 2 * MI;

/// Max length for an abort message
const MAX_LENGTH_ABORT: usize = 2 * MI;

// Import implementations
//
// This block of do_* prefixed functions is tailored for Wasmer's
Expand Down Expand Up @@ -359,6 +362,16 @@ pub fn do_debug<A: BackendApi, S: Storage, Q: Querier>(
Ok(())
}

/// Aborts the contract and shows the given error message
pub fn do_abort<A: BackendApi, S: Storage, Q: Querier>(
env: &Environment<A, S, Q>,
message_ptr: u32,
) -> VmResult<()> {
let message_data = read_region(&env.memory(), message_ptr, MAX_LENGTH_ABORT)?;
let msg = String::from_utf8_lossy(&message_data);
Err(VmError::aborted(msg))
}

/// Creates a Region in the contract, writes the given data to it and returns the memory location
fn write_to_contract<A: BackendApi, S: Storage, Q: Querier>(
env: &Environment<A, S, Q>,
Expand Down
Loading