This repository has been archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cross-contract calling: simple debugger (#14678)
* Provide basic breakpoints * Rename to Observer * Rename feature. Single trait. Borrow-checker * : frame_system::Config * Confused type name * Minor bugs * pub trait * No unnecessary cloning * Make node compile with all features * Move everything debug-related to a single module * Add docs and implementation for () * fmt * Make it feature-gated or for tests * Prepare testing kit * Testcase * Fmt * Use feature in dev-deps * ? * feature propagation * AAAA * lol, that doesn't make much sense to me * Turn on * clippy * Remove self dep * fmt, feature-gating test * Noop to trigger CI * idk * add feature to pipeline * Corrupt test to see if it is actually being run * Revert change * Doc for conf type * Review * Imports * ... * Remove debug for kitchen-sink * Move test * Fix imports * I must have already tried this one...
- Loading branch information
1 parent
6800101
commit 28e906d
Showing
9 changed files
with
216 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
#![cfg(feature = "unsafe-debug")] | ||
|
||
use super::*; | ||
use crate::unsafe_debug::{ExecutionObserver, ExportedFunction}; | ||
use frame_support::traits::Currency; | ||
use pallet_contracts_primitives::ExecReturnValue; | ||
use pretty_assertions::assert_eq; | ||
use std::cell::RefCell; | ||
|
||
#[derive(Clone, PartialEq, Eq, Debug)] | ||
struct DebugFrame { | ||
code_hash: CodeHash<Test>, | ||
call: ExportedFunction, | ||
input: Vec<u8>, | ||
result: Option<Vec<u8>>, | ||
} | ||
|
||
thread_local! { | ||
static DEBUG_EXECUTION_TRACE: RefCell<Vec<DebugFrame>> = RefCell::new(Vec::new()); | ||
} | ||
|
||
pub struct TestDebugger; | ||
|
||
impl ExecutionObserver<CodeHash<Test>> for TestDebugger { | ||
fn before_call(code_hash: &CodeHash<Test>, entry_point: ExportedFunction, input_data: &[u8]) { | ||
DEBUG_EXECUTION_TRACE.with(|d| { | ||
d.borrow_mut().push(DebugFrame { | ||
code_hash: code_hash.clone(), | ||
call: entry_point, | ||
input: input_data.to_vec(), | ||
result: None, | ||
}) | ||
}); | ||
} | ||
|
||
fn after_call( | ||
code_hash: &CodeHash<Test>, | ||
entry_point: ExportedFunction, | ||
input_data: Vec<u8>, | ||
output: &ExecReturnValue, | ||
) { | ||
DEBUG_EXECUTION_TRACE.with(|d| { | ||
d.borrow_mut().push(DebugFrame { | ||
code_hash: code_hash.clone(), | ||
call: entry_point, | ||
input: input_data, | ||
result: Some(output.data.clone()), | ||
}) | ||
}); | ||
} | ||
} | ||
|
||
#[test] | ||
fn unsafe_debugging_works() { | ||
let (wasm_caller, code_hash_caller) = compile_module::<Test>("call").unwrap(); | ||
let (wasm_callee, code_hash_callee) = compile_module::<Test>("store_call").unwrap(); | ||
|
||
fn current_stack() -> Vec<DebugFrame> { | ||
DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) | ||
} | ||
|
||
fn deploy(wasm: Vec<u8>) -> AccountId32 { | ||
Contracts::bare_instantiate( | ||
ALICE, | ||
0, | ||
GAS_LIMIT, | ||
None, | ||
Code::Upload(wasm), | ||
vec![], | ||
vec![], | ||
DebugInfo::Skip, | ||
CollectEvents::Skip, | ||
) | ||
.result | ||
.unwrap() | ||
.account_id | ||
} | ||
|
||
fn constructor_frame(hash: CodeHash<Test>, after: bool) -> DebugFrame { | ||
DebugFrame { | ||
code_hash: hash, | ||
call: ExportedFunction::Constructor, | ||
input: vec![], | ||
result: if after { Some(vec![]) } else { None }, | ||
} | ||
} | ||
|
||
fn call_frame(hash: CodeHash<Test>, args: Vec<u8>, after: bool) -> DebugFrame { | ||
DebugFrame { | ||
code_hash: hash, | ||
call: ExportedFunction::Call, | ||
input: args, | ||
result: if after { Some(vec![]) } else { None }, | ||
} | ||
} | ||
|
||
ExtBuilder::default().existential_deposit(200).build().execute_with(|| { | ||
let _ = Balances::deposit_creating(&ALICE, 1_000_000); | ||
|
||
assert_eq!(current_stack(), vec![]); | ||
|
||
let addr_caller = deploy(wasm_caller); | ||
let addr_callee = deploy(wasm_callee); | ||
|
||
assert_eq!( | ||
current_stack(), | ||
vec![ | ||
constructor_frame(code_hash_caller, false), | ||
constructor_frame(code_hash_caller, true), | ||
constructor_frame(code_hash_callee, false), | ||
constructor_frame(code_hash_callee, true), | ||
] | ||
); | ||
|
||
let main_args = (100u32, &addr_callee).encode(); | ||
let inner_args = (100u32).encode(); | ||
|
||
assert_ok!(Contracts::call( | ||
RuntimeOrigin::signed(ALICE), | ||
addr_caller, | ||
0, | ||
GAS_LIMIT, | ||
None, | ||
main_args.clone() | ||
)); | ||
|
||
let stack_top = current_stack()[4..].to_vec(); | ||
assert_eq!( | ||
stack_top, | ||
vec![ | ||
call_frame(code_hash_caller, main_args.clone(), false), | ||
call_frame(code_hash_callee, inner_args.clone(), false), | ||
call_frame(code_hash_callee, inner_args, true), | ||
call_frame(code_hash_caller, main_args, true), | ||
] | ||
); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#![cfg(feature = "unsafe-debug")] | ||
|
||
pub use crate::exec::ExportedFunction; | ||
use crate::{CodeHash, Vec}; | ||
use pallet_contracts_primitives::ExecReturnValue; | ||
|
||
/// Umbrella trait for all interfaces that serves for debugging, but are not suitable for any | ||
/// production or benchmarking use. | ||
pub trait UnsafeDebug<T: frame_system::Config>: ExecutionObserver<CodeHash<T>> {} | ||
|
||
impl<T: frame_system::Config, D> UnsafeDebug<T> for D where D: ExecutionObserver<CodeHash<T>> {} | ||
|
||
/// Defines the interface between pallet contracts and the outside observer. | ||
/// | ||
/// The intended use is the environment, where the observer holds directly the whole runtime | ||
/// (externalities) and thus can react to the execution breakpoints synchronously. | ||
/// | ||
/// This definitely *should not* be used in any production or benchmarking setting, since handling | ||
/// callbacks might be arbitrarily expensive and thus significantly influence performance. | ||
pub trait ExecutionObserver<CodeHash> { | ||
/// Called just before the execution of a contract. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `code_hash` - The code hash of the contract being called. | ||
/// * `entry_point` - Describes whether the call is the constructor or a regular call. | ||
/// * `input_data` - The raw input data of the call. | ||
fn before_call(_code_hash: &CodeHash, _entry_point: ExportedFunction, _input_data: &[u8]) {} | ||
|
||
/// Called just after the execution of a contract. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `code_hash` - The code hash of the contract being called. | ||
/// * `entry_point` - Describes whether the call was the constructor or a regular call. | ||
/// * `input_data` - The raw input data of the call. | ||
/// * `output` - The raw output of the call. | ||
fn after_call( | ||
_code_hash: &CodeHash, | ||
_entry_point: ExportedFunction, | ||
_input_data: Vec<u8>, | ||
_output: &ExecReturnValue, | ||
) { | ||
} | ||
} | ||
|
||
impl<CodeHash> ExecutionObserver<CodeHash> for () {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters