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

Add some failure modes to hackatom contract #142

Merged
merged 7 commits into from
Feb 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
47 changes: 46 additions & 1 deletion contracts/hackatom/schema/handle_msg.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "HandleMsg",
"type": "object"
"anyOf": [
{
"type": "object",
"required": [
"release"
],
"properties": {
"release": {
"type": "object"
}
}
},
{
"type": "object",
"required": [
"cpuloop"
],
"properties": {
"cpuloop": {
"type": "object"
}
}
},
{
"type": "object",
"required": [
"storageloop"
],
"properties": {
"storageloop": {
"type": "object"
}
}
},
{
"type": "object",
"required": [
"panic"
],
"properties": {
"panic": {
"type": "object"
}
}
}
]
}
81 changes: 77 additions & 4 deletions contracts/hackatom/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,20 @@ pub struct State {
pub funder: CanonicalAddr,
}

// failure modes to help test wasmd, based on this comment
// https://github.com/cosmwasm/wasmd/issues/8#issuecomment-576146751
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct HandleMsg {}
#[serde(rename_all = "lowercase")]
pub enum HandleMsg {
// Release is the only "proper" action, releasing funds in the contract
Release {},
// Infinite loop to burn cpu cycles (only run when metering is enabled)
CpuLoop {},
// Infinite loop making storage calls (to test when their limit hits)
StorageLoop {},
// Trigger a panic to ensure framework handles gracefully
Panic {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "lowercase")]
Expand Down Expand Up @@ -53,8 +65,17 @@ pub fn init<S: Storage, A: Api>(
pub fn handle<S: Storage, A: Api>(
deps: &mut Extern<S, A>,
params: Params,
_: HandleMsg,
msg: HandleMsg,
) -> Result<Response> {
match msg {
HandleMsg::Release {} => do_release(deps, params),
HandleMsg::CpuLoop {} => do_cpu_loop(),
HandleMsg::StorageLoop {} => do_storage_loop(deps),
HandleMsg::Panic {} => do_panic(),
ethanfrey marked this conversation as resolved.
Show resolved Hide resolved
}
}

fn do_release<S: Storage, A: Api>(deps: &mut Extern<S, A>, params: Params) -> Result<Response> {
let data = deps
.storage
.get(CONFIG_KEY)
Expand All @@ -79,6 +100,29 @@ pub fn handle<S: Storage, A: Api>(
}
}

fn do_cpu_loop() -> Result<Response> {
let mut counter = 0u64;
loop {
counter += 1;
if counter >= 9_000_000_000 {
counter = 0;
}
}
}

fn do_storage_loop<S: Storage, A: Api>(deps: &mut Extern<S, A>) -> Result<Response> {
let mut test_case = 0u64;
loop {
deps.storage
.set(b"test.key", test_case.to_string().as_bytes());
test_case += 1;
}
}

fn do_panic() -> Result<Response> {
panic!("This page intentionally faulted");
}

pub fn query<S: Storage, A: Api>(deps: &Extern<S, A>, msg: QueryMsg) -> Result<Vec<u8>> {
match msg {
QueryMsg::Verifier {} => query_verifier(deps),
Expand Down Expand Up @@ -216,7 +260,7 @@ mod tests {
&coin("15", "earth"),
&coin("1015", "earth"),
);
let handle_res = handle(&mut deps, handle_params, HandleMsg {}).unwrap();
let handle_res = handle(&mut deps, handle_params, HandleMsg::Release {}).unwrap();
assert_eq!(1, handle_res.messages.len());
let msg = handle_res.messages.get(0).expect("no message");
assert_eq!(
Expand Down Expand Up @@ -258,7 +302,7 @@ mod tests {
// beneficiary can release it
let handle_params =
mock_params(&deps.api, beneficiary.as_str(), &[], &coin("1000", "earth"));
let handle_res = handle(&mut deps, handle_params, HandleMsg {});
let handle_res = handle(&mut deps, handle_params, HandleMsg::Release {});
assert!(handle_res.is_err());

// state should not change
Expand All @@ -273,4 +317,33 @@ mod tests {
}
);
}

#[test]
#[should_panic(expected = "This page intentionally faulted")]
fn handle_panic() {
let mut deps = dependencies(20);

// initialize the store
let verifier = HumanAddr(String::from("verifies"));
let beneficiary = HumanAddr(String::from("benefits"));
let creator = HumanAddr(String::from("creator"));

let init_msg = InitMsg {
verifier: verifier.clone(),
beneficiary: beneficiary.clone(),
};
let init_params = mock_params(
&deps.api,
creator.as_str(),
&coin("1000", "earth"),
&coin("1000", "earth"),
);
let init_res = init(&mut deps, init_params, init_msg).unwrap();
assert_eq!(0, init_res.messages.len());

let handle_params =
mock_params(&deps.api, beneficiary.as_str(), &[], &coin("1000", "earth"));
// this should panic
let _ = handle(&mut deps, handle_params, HandleMsg::Panic {});
}
}
57 changes: 52 additions & 5 deletions contracts/hackatom/tests/integration.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::str::from_utf8;

use cosmwasm::mock::mock_params;
use cosmwasm::serde::from_slice;
use cosmwasm::serde::{from_slice, to_vec};
use cosmwasm::traits::{Api, ReadonlyStorage};
use cosmwasm::types::{coin, CosmosMsg, HumanAddr, QueryResult};

use cosmwasm_vm::call_handle;
use cosmwasm_vm::testing::{handle, init, mock_instance, query};

use hackatom::contract::{HandleMsg, InitMsg, QueryMsg, State, CONFIG_KEY};
Expand Down Expand Up @@ -84,7 +85,7 @@ fn init_and_query() {
assert_eq!(verifier.as_str(), returned);

// bad query returns parse error (pass wrong type - this connection is not enforced)
let qres = query(&mut deps, HandleMsg {});
let qres = query(&mut deps, HandleMsg::Release {});
match qres {
QueryResult::Err(msg) => assert!(msg.starts_with("Error parsing QueryMsg:"), msg),
_ => panic!("Call should fail"),
Expand All @@ -96,7 +97,7 @@ fn fails_on_bad_init() {
let mut deps = mock_instance(WASM);
let params = mock_params(&deps.api, "creator", &coin("1000", "earth"), &[]);
// bad init returns parse error (pass wrong type - this connection is not enforced)
let res = init(&mut deps, params, HandleMsg {});
let res = init(&mut deps, params, HandleMsg::Release {});
assert_eq!(true, res.is_err());
}

Expand Down Expand Up @@ -128,7 +129,7 @@ fn proper_handle() {
&coin("15", "earth"),
&coin("1015", "earth"),
);
let handle_res = handle(&mut deps, handle_params, HandleMsg {}).unwrap();
let handle_res = handle(&mut deps, handle_params, HandleMsg::Release {}).unwrap();
assert_eq!(1, handle_res.messages.len());
let msg = handle_res.messages.get(0).expect("no message");
assert_eq!(
Expand Down Expand Up @@ -169,7 +170,7 @@ fn failed_handle() {

// beneficiary can release it
let handle_params = mock_params(&deps.api, beneficiary.as_str(), &[], &coin("1000", "earth"));
let handle_res = handle(&mut deps, handle_params, HandleMsg {});
let handle_res = handle(&mut deps, handle_params, HandleMsg::Release {});
assert!(handle_res.is_err());

// state should not change
Expand All @@ -186,3 +187,49 @@ fn failed_handle() {
);
});
}

#[test]
fn handle_panic_and_loops() {
let mut deps = mock_instance(WASM);
// Gas must be set so we die early on infinite loop
deps.set_gas(1_000_000);

// initialize the store
let verifier = HumanAddr(String::from("verifies"));
let beneficiary = HumanAddr(String::from("benefits"));
let creator = HumanAddr(String::from("creator"));

let init_msg = InitMsg {
verifier: verifier.clone(),
beneficiary: beneficiary.clone(),
};
let init_params = mock_params(
&deps.api,
creator.as_str(),
&coin("1000", "earth"),
&coin("1000", "earth"),
);
let init_res = init(&mut deps, init_params, init_msg).unwrap();
assert_eq!(0, init_res.messages.len());

// TRY PANIC
let handle_params = mock_params(&deps.api, beneficiary.as_str(), &[], &coin("1000", "earth"));
// panic inside contract should not panic out here
// Note: we need to use the production-call, not the testing call (which unwraps any vm error)
let handle_res = call_handle(
&mut deps,
&handle_params,
&to_vec(&HandleMsg::Panic {}).unwrap(),
);
assert!(handle_res.is_err());

// TRY INFINITE LOOP
// Note: we need to use the production-call, not the testing call (which unwraps any vm error)
let handle_res = call_handle(
&mut deps,
&handle_params,
&to_vec(&HandleMsg::CpuLoop {}).unwrap(),
);
assert!(handle_res.is_err());
assert_eq!(deps.get_gas(), 0);
}
12 changes: 10 additions & 2 deletions lib/vm/src/backends/singlepass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,19 @@ pub fn backend() -> Backend {
}

pub fn set_gas(instance: &mut Instance, limit: u64) {
let used = GAS_LIMIT - limit;
let used = if limit > GAS_LIMIT {
0
} else {
GAS_LIMIT - limit
};
ethanfrey marked this conversation as resolved.
Show resolved Hide resolved
metering::set_points_used(instance, used)
}

pub fn get_gas(instance: &Instance) -> u64 {
let used = metering::get_points_used(instance);
GAS_LIMIT - used
if used > GAS_LIMIT {
0
} else {
GAS_LIMIT - used
}
}
6 changes: 3 additions & 3 deletions lib/vm/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ mod test {
&coin("15", "earth"),
&coin("1015", "earth"),
);
let msg = b"{}";
let msg = br#"{"release":{}}"#;
let res = call_handle(&mut instance, &params, msg).unwrap();
let msgs = res.unwrap().messages;
assert_eq!(1, msgs.len());
Expand Down Expand Up @@ -230,7 +230,7 @@ mod test {
&coin("15", "earth"),
&coin("1015", "earth"),
);
let msg = b"{}";
let msg = br#"{"release":{}}"#;
let res = call_handle(&mut instance, &params, msg).unwrap();
let msgs = res.unwrap().messages;
assert_eq!(1, msgs.len());
Expand All @@ -244,7 +244,7 @@ mod test {
&coin("15", "earth"),
&coin("1015", "earth"),
);
let msg = b"{}";
let msg = br#"{"release":{}}"#;
let res = call_handle(&mut instance, &params, msg).unwrap();
let msgs = res.unwrap().messages;
assert_eq!(1, msgs.len());
Expand Down
6 changes: 3 additions & 3 deletions lib/vm/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ mod test {

let init_used = orig_gas - instance.get_gas();
println!("init used: {}", init_used);
assert_eq!(init_used, 66_763);
assert_eq!(init_used, 66_765);

// run contract - just sanity check - results validate in contract unit tests
instance.set_gas(orig_gas);
Expand All @@ -176,14 +176,14 @@ mod test {
&coin("15", "earth"),
&coin("1015", "earth"),
);
let msg = b"{}";
let msg = br#"{"release":{}}"#;
let res = call_handle(&mut instance, &params, msg).unwrap();
let msgs = res.unwrap().messages;
assert_eq!(1, msgs.len());

let handle_used = orig_gas - instance.get_gas();
println!("handle used: {}", handle_used);
assert_eq!(handle_used, 110_267);
assert_eq!(handle_used, 112_832);
}

#[test]
Expand Down
Binary file modified lib/vm/testdata/contract.wasm
Binary file not shown.