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...
This commit is contained in:
Piotr Mikołajczyk
2023-08-05 16:15:03 +02:00
committed by GitHub
parent 32bd1c397b
commit b24b66c991
9 changed files with 216 additions and 3 deletions
@@ -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),
]
);
});
}