Skip to content

Commit

Permalink
Merge pull request #142 from confio/hackatom-with-failure-modes
Browse files Browse the repository at this point in the history
Add some failure modes to hackatom contract
  • Loading branch information
ethanfrey authored Feb 9, 2020
2 parents 69d73c7 + 4dcfd3b commit 3351c30
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 18 deletions.
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(),
}
}

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
};
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.

0 comments on commit 3351c30

Please sign in to comment.