From 322fe8358a286c625f6c457ad57d04519f19f487 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 16 Dec 2024 18:49:21 +0100 Subject: [PATCH 01/14] test: add a new test lib for detecting oom issues --- clar2wasm/src/tools.rs | 2 +- clar2wasm/tests/oom-checker/main.rs | 67 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 clar2wasm/tests/oom-checker/main.rs diff --git a/clar2wasm/src/tools.rs b/clar2wasm/src/tools.rs index 69b420f4..2104f213 100644 --- a/clar2wasm/src/tools.rs +++ b/clar2wasm/src/tools.rs @@ -373,7 +373,7 @@ pub fn interpret(snippet: &str) -> Result, Error> { interpret_at(snippet, StacksEpochId::latest(), ClarityVersion::latest()) } -struct TestConfig; +pub struct TestConfig; impl TestConfig { /// Select a Clarity version based on enabled features. diff --git a/clar2wasm/tests/oom-checker/main.rs b/clar2wasm/tests/oom-checker/main.rs new file mode 100644 index 00000000..3d1decf4 --- /dev/null +++ b/clar2wasm/tests/oom-checker/main.rs @@ -0,0 +1,67 @@ +use clar2wasm::compile; +use clar2wasm::datastore::Datastore; +use clar2wasm::tools::{crosscheck, TestConfig}; +use clarity::vm::costs::LimitedCostTracker; +use clarity::vm::errors::{CheckErrors, Error}; +use clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData}; +use clarity::vm::Value; + +pub fn as_oom_check_snippet(snippet: &str) -> String { + let version = TestConfig::clarity_version(); + let epoch = TestConfig::latest_epoch(); + + let compiled_module = Datastore::new() + .as_analysis_db() + .execute(|analysis_db| { + compile( + snippet, + &QualifiedContractIdentifier::new( + StandardPrincipalData::transient(), + ("foo").into(), + ), + LimitedCostTracker::new_free(), + version, + epoch, + analysis_db, + ) + .map_err(|e| CheckErrors::Expects(format!("Compilation failure {e:?}"))) + }) + .expect("Could not compile snippet") + .module; + + let memory_pages = compiled_module + .memories + .iter() + .next() + .expect("Couldn't find a memory") + .initial as usize; + let stack_pointer_value = match compiled_module + .globals + .iter() + .find(|g| g.name.as_ref().is_some_and(|name| name == "stack-pointer")) + .expect("Couldn't find stack-pointer global") + .kind + { + walrus::GlobalKind::Local(walrus::InitExpr::Value(walrus::ir::Value::I32(val))) => { + val as usize + } + _ => unreachable!("stack-pointer should be a locally declared global with a i32 value"), + }; + + let free_space_on_memory_page = memory_pages * 65536 - stack_pointer_value; + + dbg!(format!( + "(define-constant ignore 0x{})\n{snippet}", + "00".repeat({ + // we will need 8 bytes for the (offset, size) and 6 bytes for the name "ignore" + free_space_on_memory_page + .checked_sub(14) + // we add a page if we don't have 14 remaining bytes + .unwrap_or(free_space_on_memory_page + 65536 - 8 - 6) + }) + )) +} + +pub fn crosscheck_oom(snippet: &str, expected: Result, Error>) { + crosscheck(&as_oom_check_snippet(snippet), expected); +} From 6217ec6e827abe5d5d12e330cf4f1ad8f4413577 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 16 Dec 2024 18:52:42 +0100 Subject: [PATCH 02/14] test: add a oom test for principal-of? --- clar2wasm/tests/oom-checker/main.rs | 2 ++ clar2wasm/tests/oom-checker/unit_tests.rs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 clar2wasm/tests/oom-checker/unit_tests.rs diff --git a/clar2wasm/tests/oom-checker/main.rs b/clar2wasm/tests/oom-checker/main.rs index 3d1decf4..3f610173 100644 --- a/clar2wasm/tests/oom-checker/main.rs +++ b/clar2wasm/tests/oom-checker/main.rs @@ -1,3 +1,5 @@ +pub mod unit_tests; + use clar2wasm::compile; use clar2wasm::datastore::Datastore; use clar2wasm::tools::{crosscheck, TestConfig}; diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs new file mode 100644 index 00000000..c4fe66f7 --- /dev/null +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -0,0 +1,20 @@ +use clarity::vm::types::PrincipalData; +use clarity::vm::Value; + +use crate::crosscheck_oom; + +#[test] +#[ignore = "issue #585"] +fn principal_of_oom() { + crosscheck_oom( + "(principal-of? 0x03adb8de4bfb65db2cfd6120d55c6526ae9c52e675db7e47308636534ba7786110)", + Ok(Some( + Value::okay( + PrincipalData::parse("ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP") + .unwrap() + .into(), + ) + .unwrap(), + )), + ) +} From 7a43885ac5c445357d8510b558b7442e915d026b Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 16 Dec 2024 19:07:40 +0100 Subject: [PATCH 03/14] chore: clippy lint ignored --- clar2wasm/tests/oom-checker/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clar2wasm/tests/oom-checker/main.rs b/clar2wasm/tests/oom-checker/main.rs index 3f610173..c796bbd8 100644 --- a/clar2wasm/tests/oom-checker/main.rs +++ b/clar2wasm/tests/oom-checker/main.rs @@ -8,6 +8,7 @@ use clarity::vm::errors::{CheckErrors, Error}; use clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData}; use clarity::vm::Value; +#[allow(clippy::expect_used)] pub fn as_oom_check_snippet(snippet: &str) -> String { let version = TestConfig::clarity_version(); let epoch = TestConfig::latest_epoch(); From c9d3c112df600acd063d92b825a646a9e5a6acd8 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Tue, 17 Dec 2024 16:57:53 +0100 Subject: [PATCH 04/14] test: add crosscheck_oom for functions with arguments computed at runtime --- clar2wasm/tests/oom-checker/main.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/clar2wasm/tests/oom-checker/main.rs b/clar2wasm/tests/oom-checker/main.rs index c796bbd8..8b4b4734 100644 --- a/clar2wasm/tests/oom-checker/main.rs +++ b/clar2wasm/tests/oom-checker/main.rs @@ -1,15 +1,17 @@ +#![cfg(test)] pub mod unit_tests; use clar2wasm::compile; use clar2wasm::datastore::Datastore; use clar2wasm::tools::{crosscheck, TestConfig}; +use clar2wasm::wasm_utils::get_type_in_memory_size; use clarity::vm::costs::LimitedCostTracker; use clarity::vm::errors::{CheckErrors, Error}; -use clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData}; +use clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData, TypeSignature}; use clarity::vm::Value; #[allow(clippy::expect_used)] -pub fn as_oom_check_snippet(snippet: &str) -> String { +pub fn as_oom_check_snippet(snippet: &str, args_types: &[TypeSignature]) -> String { let version = TestConfig::clarity_version(); let epoch = TestConfig::latest_epoch(); @@ -51,20 +53,33 @@ pub fn as_oom_check_snippet(snippet: &str) -> String { _ => unreachable!("stack-pointer should be a locally declared global with a i32 value"), }; + let args_space_needed = args_types + .iter() + .map(|ty| get_type_in_memory_size(ty, false)) + .sum::() as usize; + let free_space_on_memory_page = memory_pages * 65536 - stack_pointer_value; - dbg!(format!( + format!( "(define-constant ignore 0x{})\n{snippet}", "00".repeat({ // we will need 8 bytes for the (offset, size) and 6 bytes for the name "ignore" free_space_on_memory_page - .checked_sub(14) + .checked_sub(14 + args_space_needed) // we add a page if we don't have 14 remaining bytes - .unwrap_or(free_space_on_memory_page + 65536 - 8 - 6) + .unwrap_or(free_space_on_memory_page + 65536 - 14 - args_space_needed) }) - )) + ) +} + +pub fn crosscheck_oom_with_non_literal_args( + snippet: &str, + args_types: &[TypeSignature], + expected: Result, Error>, +) { + crosscheck(&as_oom_check_snippet(snippet, args_types), expected); } pub fn crosscheck_oom(snippet: &str, expected: Result, Error>) { - crosscheck(&as_oom_check_snippet(snippet), expected); + crosscheck_oom_with_non_literal_args(snippet, &[], expected) } From 97744498fd7b23f7716b17f40de9f38b7dd39c85 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Wed, 18 Dec 2024 14:56:46 +0100 Subject: [PATCH 05/14] test: add oom tests for sequences functions --- clar2wasm/tests/oom-checker/main.rs | 10 ++- clar2wasm/tests/oom-checker/unit_tests.rs | 80 ++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/clar2wasm/tests/oom-checker/main.rs b/clar2wasm/tests/oom-checker/main.rs index 8b4b4734..7fb53dc2 100644 --- a/clar2wasm/tests/oom-checker/main.rs +++ b/clar2wasm/tests/oom-checker/main.rs @@ -7,7 +7,9 @@ use clar2wasm::tools::{crosscheck, TestConfig}; use clar2wasm::wasm_utils::get_type_in_memory_size; use clarity::vm::costs::LimitedCostTracker; use clarity::vm::errors::{CheckErrors, Error}; -use clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData, TypeSignature}; +use clarity::vm::types::{ + ListTypeData, QualifiedContractIdentifier, StandardPrincipalData, TypeSignature, +}; use clarity::vm::Value; #[allow(clippy::expect_used)] @@ -83,3 +85,9 @@ pub fn crosscheck_oom_with_non_literal_args( pub fn crosscheck_oom(snippet: &str, expected: Result, Error>) { crosscheck_oom_with_non_literal_args(snippet, &[], expected) } + +pub(crate) fn list_of(elem: TypeSignature, max_len: u32) -> TypeSignature { + TypeSignature::SequenceType(clarity::vm::types::SequenceSubtype::ListType( + ListTypeData::new_list(elem, max_len).unwrap(), + )) +} diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index c4fe66f7..4a1c5973 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -1,7 +1,7 @@ -use clarity::vm::types::PrincipalData; +use clarity::vm::types::{ListTypeData, PrincipalData, SequenceSubtype, TypeSignature}; use clarity::vm::Value; -use crate::crosscheck_oom; +use crate::{crosscheck_oom, crosscheck_oom_with_non_literal_args, list_of}; #[test] #[ignore = "issue #585"] @@ -18,3 +18,79 @@ fn principal_of_oom() { )), ) } + +#[test] +fn list_oom() { + crosscheck_oom( + "(list 1 2 3)", + Ok(Some( + Value::cons_list_unsanitized(vec![Value::Int(1), Value::Int(2), Value::Int(3)]) + .unwrap(), + )), + ); +} + +#[test] +fn append_oom() { + crosscheck_oom_with_non_literal_args( + "(append (list 1 2 3) 4)", + &[list_of(TypeSignature::IntType, 3)], + Ok(Some( + Value::cons_list_unsanitized(vec![ + Value::Int(1), + Value::Int(2), + Value::Int(3), + Value::Int(4), + ]) + .unwrap(), + )), + ); +} + +#[test] +fn concat_oom() { + crosscheck_oom_with_non_literal_args( + "(concat (list 1 2 3) (list 4 5))", + &[ + list_of(TypeSignature::IntType, 3), + list_of(TypeSignature::IntType, 2), + ], + Ok(Some( + Value::cons_list_unsanitized(vec![ + Value::Int(1), + Value::Int(2), + Value::Int(3), + Value::Int(4), + Value::Int(5), + ]) + .unwrap(), + )), + ); +} + +#[test] +fn replace_at_oom() { + crosscheck_oom_with_non_literal_args( + "(replace-at? (list 1 2 3) u0 42)", + &[list_of(TypeSignature::IntType, 3)], + Ok(Some( + Value::some( + Value::cons_list_unsanitized(vec![Value::Int(42), Value::Int(2), Value::Int(3)]) + .unwrap(), + ) + .unwrap(), + )), + ); +} + +#[test] +fn map_oom() { + crosscheck_oom_with_non_literal_args( + "(define-private (foo (b bool)) u42) (map foo (list true true false))", + &[list_of(TypeSignature::BoolType, 3)], + Ok(Some( + Value::cons_list_unsanitized(vec![Value::UInt(42), Value::UInt(42), Value::UInt(42)]) + .unwrap(), + )), + ) +} From e73bdf1ed720edd4859ac05040e11d2c72df0342 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 16:11:56 +0100 Subject: [PATCH 06/14] test: finish oom testing of sequences --- clar2wasm/tests/oom-checker/unit_tests.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index 4a1c5973..5170bd90 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -86,11 +86,21 @@ fn replace_at_oom() { #[test] fn map_oom() { crosscheck_oom_with_non_literal_args( - "(define-private (foo (b bool)) u42) (map foo (list true true false))", + "(define-private (foo (b bool)) (if b u1 u0)) (map foo (list true true false))", &[list_of(TypeSignature::BoolType, 3)], Ok(Some( - Value::cons_list_unsanitized(vec![Value::UInt(42), Value::UInt(42), Value::UInt(42)]) + Value::cons_list_unsanitized(vec![Value::UInt(1), Value::UInt(1), Value::UInt(0)]) .unwrap(), )), ) } + +#[test] +fn fold_oom() { + let snippet = r#" +(define-private (concat-buff (a (buff 1)) (b (buff 3))) + (unwrap-panic (as-max-len? (concat a b) u3))) +(fold concat-buff 0x010203 0x) + "#; + crosscheck_oom(snippet, Ok(Some(Value::buff_from(vec![3, 2, 1]).unwrap()))); +} From b3a5a5e2dc54bef6dc408aaa342a056fc6e41cda Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 17:28:07 +0100 Subject: [PATCH 07/14] test: fix oom creator to pass epoch and version as arguments --- clar2wasm/src/tools.rs | 4 ++-- clar2wasm/tests/oom-checker/main.rs | 37 +++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/clar2wasm/src/tools.rs b/clar2wasm/src/tools.rs index 2104f213..414b3fa7 100644 --- a/clar2wasm/src/tools.rs +++ b/clar2wasm/src/tools.rs @@ -27,8 +27,8 @@ use crate::initialize::initialize_contract; #[derive(Clone)] pub struct TestEnvironment { contract_contexts: HashMap, - epoch: StacksEpochId, - version: ClarityVersion, + pub epoch: StacksEpochId, + pub version: ClarityVersion, datastore: Datastore, burn_datastore: BurnDatastore, cost_tracker: LimitedCostTracker, diff --git a/clar2wasm/tests/oom-checker/main.rs b/clar2wasm/tests/oom-checker/main.rs index 7fb53dc2..f839d1fc 100644 --- a/clar2wasm/tests/oom-checker/main.rs +++ b/clar2wasm/tests/oom-checker/main.rs @@ -3,20 +3,23 @@ pub mod unit_tests; use clar2wasm::compile; use clar2wasm::datastore::Datastore; -use clar2wasm::tools::{crosscheck, TestConfig}; +use clar2wasm::tools::{crosscheck, crosscheck_with_env, TestConfig, TestEnvironment}; use clar2wasm::wasm_utils::get_type_in_memory_size; +use clarity::types::StacksEpochId; use clarity::vm::costs::LimitedCostTracker; use clarity::vm::errors::{CheckErrors, Error}; use clarity::vm::types::{ ListTypeData, QualifiedContractIdentifier, StandardPrincipalData, TypeSignature, }; -use clarity::vm::Value; +use clarity::vm::{ClarityVersion, Value}; #[allow(clippy::expect_used)] -pub fn as_oom_check_snippet(snippet: &str, args_types: &[TypeSignature]) -> String { - let version = TestConfig::clarity_version(); - let epoch = TestConfig::latest_epoch(); - +pub fn as_oom_check_snippet( + snippet: &str, + args_types: &[TypeSignature], + epoch: StacksEpochId, + version: ClarityVersion, +) -> String { let compiled_module = Datastore::new() .as_analysis_db() .execute(|analysis_db| { @@ -79,13 +82,33 @@ pub fn crosscheck_oom_with_non_literal_args( args_types: &[TypeSignature], expected: Result, Error>, ) { - crosscheck(&as_oom_check_snippet(snippet, args_types), expected); + crosscheck( + &as_oom_check_snippet( + snippet, + args_types, + TestConfig::latest_epoch(), + TestConfig::clarity_version(), + ), + expected, + ); } pub fn crosscheck_oom(snippet: &str, expected: Result, Error>) { crosscheck_oom_with_non_literal_args(snippet, &[], expected) } +pub fn crosscheck_oom_with_env( + snippet: &str, + expected: Result, Error>, + env: TestEnvironment, +) { + crosscheck_with_env( + &as_oom_check_snippet(snippet, &[], env.epoch, env.version), + expected, + env, + ); +} + pub(crate) fn list_of(elem: TypeSignature, max_len: u32) -> TypeSignature { TypeSignature::SequenceType(clarity::vm::types::SequenceSubtype::ListType( ListTypeData::new_list(elem, max_len).unwrap(), From 541819987394c02b88ae4ac3ec91c3357bda5d0d Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 17:28:47 +0100 Subject: [PATCH 08/14] test: check oom for blockinfo functions --- clar2wasm/tests/oom-checker/unit_tests.rs | 140 +++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index 5170bd90..0aea44b5 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -1,7 +1,10 @@ +use clar2wasm::tools::TestEnvironment; use clarity::vm::types::{ListTypeData, PrincipalData, SequenceSubtype, TypeSignature}; use clarity::vm::Value; -use crate::{crosscheck_oom, crosscheck_oom_with_non_literal_args, list_of}; +use crate::{ + crosscheck_oom, crosscheck_oom_with_env, crosscheck_oom_with_non_literal_args, list_of, +}; #[test] #[ignore = "issue #585"] @@ -104,3 +107,138 @@ fn fold_oom() { "#; crosscheck_oom(snippet, Ok(Some(Value::buff_from(vec![3, 2, 1]).unwrap()))); } + +#[test] +fn get_block_info_burnchain_header_hash_oom() { + let mut env = TestEnvironment::new( + clarity::types::StacksEpochId::Epoch25, + clarity::vm::ClarityVersion::Clarity2, + ); + env.advance_chain_tip(1); + + crosscheck_oom_with_env( + "(get-block-info? burnchain-header-hash u0)", + Ok(Some( + Value::some(Value::buff_from(vec![0; 32]).unwrap()).unwrap(), + )), + env, + ); +} + +#[test] +fn get_block_info_id_header_hash_oom() { + let mut env = TestEnvironment::new( + clarity::types::StacksEpochId::Epoch25, + clarity::vm::ClarityVersion::Clarity2, + ); + env.advance_chain_tip(1); + + crosscheck_oom_with_env( + "(get-block-info? id-header-hash u0)", + Ok(Some( + Value::some( + // same result as in get_block_info_header_hash() test + Value::buff_from(vec![ + 181, 224, 118, 171, 118, 9, 199, 248, 199, 99, 181, 197, 113, 208, 122, 234, + 128, 176, 107, 65, 69, 34, 49, 177, 67, 115, 112, 244, 150, 78, 214, 110, + ]) + .unwrap(), + ) + .unwrap(), + )), + env, + ); +} + +#[test] +fn get_block_info_header_hash_oom() { + let mut env = TestEnvironment::new( + clarity::types::StacksEpochId::Epoch25, + clarity::vm::ClarityVersion::Clarity2, + ); + env.advance_chain_tip(1); + + crosscheck_oom_with_env( + "(get-block-info? header-hash u0)", + Ok(Some( + Value::some(Value::buff_from(vec![0; 32]).unwrap()).unwrap(), + )), + env, + ); +} + +#[test] +fn get_block_info_miner_address_oom() { + let mut env = TestEnvironment::new( + clarity::types::StacksEpochId::Epoch25, + clarity::vm::ClarityVersion::Clarity2, + ); + env.advance_chain_tip(1); + + crosscheck_oom_with_env( + "(get-block-info? miner-address u0)", + Ok(Some( + Value::some(Value::Principal( + PrincipalData::parse("ST000000000000000000002AMW42H").unwrap(), + )) + .unwrap(), + )), + env, + ); +} + +#[test] +fn get_burn_block_info_header_hash_oom() { + let mut env = TestEnvironment::new( + clarity::types::StacksEpochId::Epoch25, + clarity::vm::ClarityVersion::Clarity2, + ); + env.advance_chain_tip(1); + + crosscheck_oom_with_env( + "(get-burn-block-info? header-hash u0)", + Ok(Some( + Value::some(Value::buff_from(vec![0; 32]).unwrap()).unwrap(), + )), + env, + ); +} + +#[test] +fn get_burn_block_info_pox_addrs_oom() { + let mut env = TestEnvironment::new( + clarity::types::StacksEpochId::Epoch25, + clarity::vm::ClarityVersion::Clarity2, + ); + env.advance_chain_tip(1); + + crosscheck_oom_with_env( + "(get-burn-block-info? pox-addrs u0)", + Ok(Some( + Value::some( + clarity::vm::types::TupleData::from_data(vec![ + ( + "addrs".into(), + Value::cons_list_unsanitized(vec![ + clarity::vm::types::TupleData::from_data(vec![ + ( + "hashbytes".into(), + Value::buff_from([0; 32].to_vec()).unwrap(), + ), + ("version".into(), Value::buff_from_byte(0)), + ]) + .unwrap() + .into(), + ]) + .unwrap(), + ), + ("payout".into(), Value::UInt(0)), + ]) + .unwrap() + .into(), + ) + .unwrap(), + )), + env, + ); +} From 90c90e8650b27b44d2fc7c2b918f356acf60aac1 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 17:41:55 +0100 Subject: [PATCH 09/14] test: oom check for int-to-string/utf8 functions --- clar2wasm/tests/oom-checker/unit_tests.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index 0aea44b5..7e1bff4e 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -242,3 +242,23 @@ fn get_burn_block_info_pox_addrs_oom() { env, ); } + +#[test] +#[ignore = "issue #592"] +fn int_to_ascii_oom() { + crosscheck_oom( + "(int-to-ascii 42)", + Ok(Some( + Value::string_ascii_from_bytes(b"42".to_vec()).unwrap(), + )), + ); +} + +#[test] +#[ignore = "issue #592"] +fn int_to_utf8_oom() { + crosscheck_oom( + "(int-to-utf8 42)", + Ok(Some(Value::string_utf8_from_bytes(b"42".to_vec()).unwrap())), + ); +} From 9d21992ca0461255d5148fdf71208c7636b3d670 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 18:21:59 +0100 Subject: [PATCH 10/14] test: oom check for data-vars --- clar2wasm/tests/oom-checker/unit_tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index 7e1bff4e..05876f38 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -262,3 +262,15 @@ fn int_to_utf8_oom() { Ok(Some(Value::string_utf8_from_bytes(b"42".to_vec()).unwrap())), ); } + +#[test] +fn data_var_oom() { + crosscheck_oom( + r#" + (define-data-var n (buff 1) 0x) + (var-set n 0x42) + (var-get n) + "#, + Ok(Some(Value::buff_from_byte(0x42))), + ); +} From d72564ca244d6993b76658d1e58cebaae40505d8 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 18:36:11 +0100 Subject: [PATCH 11/14] test: add oom check on secp256k1-recover --- clar2wasm/tests/oom-checker/unit_tests.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index 05876f38..701aa826 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -274,3 +274,11 @@ fn data_var_oom() { Ok(Some(Value::buff_from_byte(0x42))), ); } + +#[test] +fn secp256k1_recover_oom() { + crosscheck_oom( + "(secp256k1-recover? 0xde5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f04 0x8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a1301)", + Ok(Some(Value::okay(Value::buff_from(vec![3, 173, 184, 222, 75, 251, 101, 219, 44, 253, 97, 32, 213, 92, 101, 38, 174, 156, 82, 230, 117, 219, 126, 71, 48, 134, 54, 83, 75, 167, 120, 97, 16]).unwrap()).unwrap())), + ); +} From a1e39ff41b172b105c701daf0195994e9a9bae36 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 18:40:57 +0100 Subject: [PATCH 12/14] chore: clippy pleasing --- clar2wasm/tests/oom-checker/unit_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index 701aa826..8573ed8c 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -1,5 +1,5 @@ use clar2wasm::tools::TestEnvironment; -use clarity::vm::types::{ListTypeData, PrincipalData, SequenceSubtype, TypeSignature}; +use clarity::vm::types::{PrincipalData, TypeSignature}; use clarity::vm::Value; use crate::{ From aab3c68f24afced60b8e1b79bfa001a22ee48ea6 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Mon, 30 Dec 2024 18:44:10 +0100 Subject: [PATCH 13/14] test: ignore oom test for replace-at in clarity v1 --- clar2wasm/tests/oom-checker/unit_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clar2wasm/tests/oom-checker/unit_tests.rs b/clar2wasm/tests/oom-checker/unit_tests.rs index 8573ed8c..c777b3ce 100644 --- a/clar2wasm/tests/oom-checker/unit_tests.rs +++ b/clar2wasm/tests/oom-checker/unit_tests.rs @@ -71,6 +71,7 @@ fn concat_oom() { ); } +#[cfg(not(feature = "test-clarity-v1"))] #[test] fn replace_at_oom() { crosscheck_oom_with_non_literal_args( From 5b1a8fbd310c06d50cfe4f2749b84da329375e47 Mon Sep 17 00:00:00 2001 From: Anthony Caccia Date: Tue, 7 Jan 2025 13:39:13 +0100 Subject: [PATCH 14/14] chore: doc + comments + DRY removal --- clar2wasm/tests/oom-checker/main.rs | 36 +++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/clar2wasm/tests/oom-checker/main.rs b/clar2wasm/tests/oom-checker/main.rs index f839d1fc..8ab52f9a 100644 --- a/clar2wasm/tests/oom-checker/main.rs +++ b/clar2wasm/tests/oom-checker/main.rs @@ -13,8 +13,18 @@ use clarity::vm::types::{ }; use clarity::vm::{ClarityVersion, Value}; +/// Name of the buffer that will fill the empty space. +const IGNORE_BUFFER_NAME: &str = "ignore"; +/// Size in memory for the buffer that will fill the empty space's (offset, len). +const IGNORE_BUFFER_SIZE: usize = 8; +/// Minimum size needed in memory to create a filling buffer +const IGNORE_BUFFER_MIN_SIZE_NEEDED: usize = IGNORE_BUFFER_SIZE + IGNORE_BUFFER_NAME.len(); + +/// Size of a page in Wasm +const WASM_PAGE_SIZE: usize = 65536; + #[allow(clippy::expect_used)] -pub fn as_oom_check_snippet( +fn as_oom_check_snippet( snippet: &str, args_types: &[TypeSignature], epoch: StacksEpochId, @@ -39,12 +49,14 @@ pub fn as_oom_check_snippet( .expect("Could not compile snippet") .module; + // we look for the total number of pages that were allocated for the module. let memory_pages = compiled_module .memories .iter() .next() .expect("Couldn't find a memory") .initial as usize; + // we look for the first byte in memory which doesn't contain useful data. let stack_pointer_value = match compiled_module .globals .iter() @@ -58,25 +70,29 @@ pub fn as_oom_check_snippet( _ => unreachable!("stack-pointer should be a locally declared global with a i32 value"), }; + // WORKAROUND: this is to ignore arguments that are computed at runtime and should be removed after fixing + // [issue #587](https://github.com/stacks-network/clarity-wasm/issues/587) let args_space_needed = args_types .iter() .map(|ty| get_type_in_memory_size(ty, false)) .sum::() as usize; - let free_space_on_memory_page = memory_pages * 65536 - stack_pointer_value; + // the free space on the last page that we want to fill is the substraction of the total number of bytes + // for all the available pages and the last byte which will contain useful data. + let mut free_space_on_memory_page = memory_pages * WASM_PAGE_SIZE - stack_pointer_value; + + let total_space_needed = IGNORE_BUFFER_MIN_SIZE_NEEDED + args_space_needed; + if free_space_on_memory_page < total_space_needed { + free_space_on_memory_page += WASM_PAGE_SIZE; + } format!( - "(define-constant ignore 0x{})\n{snippet}", - "00".repeat({ - // we will need 8 bytes for the (offset, size) and 6 bytes for the name "ignore" - free_space_on_memory_page - .checked_sub(14 + args_space_needed) - // we add a page if we don't have 14 remaining bytes - .unwrap_or(free_space_on_memory_page + 65536 - 14 - args_space_needed) - }) + "(define-constant {IGNORE_BUFFER_NAME} 0x{})\n{snippet}", + "00".repeat(free_space_on_memory_page - total_space_needed) ) } +// TODO: deprecate after fixing [issue #587](https://github.com/stacks-network/clarity-wasm/issues/587) pub fn crosscheck_oom_with_non_literal_args( snippet: &str, args_types: &[TypeSignature],