Enable mocking contracts (#1331)

# Description
This PR introduces two changes:
- the previous `Tracing` trait has been modified to accept contract
address instead of code hash (seems to be way more convenient)
- a new trait `CallInterceptor` that allows intercepting contract calls;
in particular the default implementation for `()` will just proceed in a
standard way (after compilation optimizations, there will be no
footprint of that); however, implementing type might decide to mock
invocation and return `ExecResult` instead

Note: one might try merging `before_call` and `intercept_call`. However,
IMHO this would be bad, since it would mix two completely different
abstractions - tracing without any effects and actual intervention into
execution process.

This will unblock working on mocking contracts utility in drink and
similar tools (https://github.com/Cardinal-Cryptography/drink/issues/33)

# Checklist

- [x] My PR includes a detailed description as outlined in the
"Description" section above
- [ ] My PR follows the [labeling
requirements](https://github.com/paritytech/polkadot-sdk/blob/master/docs/CONTRIBUTING.md#process)
of this project (at minimum one label for `T` required)
- [x] I have made corresponding changes to the documentation (if
applicable)
- [x] I have added tests that prove my fix is effective or that my
feature works (if applicable)
This commit is contained in:
Piotr Mikołajczyk
2023-09-29 15:39:13 +02:00
committed by GitHub
parent ef3adf9a01
commit d8d90a82a7
3 changed files with 148 additions and 40 deletions
+47 -9
View File
@@ -15,14 +15,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub use crate::exec::ExportedFunction;
use crate::{CodeHash, Config, LOG_TARGET};
use pallet_contracts_primitives::ExecReturnValue;
pub use crate::exec::{ExecResult, ExportedFunction};
use crate::{Config, LOG_TARGET};
pub use pallet_contracts_primitives::ExecReturnValue;
/// Umbrella trait for all interfaces that serves for debugging.
pub trait Debugger<T: Config>: Tracing<T> {}
pub trait Debugger<T: Config>: Tracing<T> + CallInterceptor<T> {}
impl<T: Config, V> Debugger<T> for V where V: Tracing<T> {}
impl<T: Config, V> Debugger<T> for V where V: Tracing<T> + CallInterceptor<T> {}
/// Defines methods to capture contract calls, enabling external observers to
/// measure, trace, and react to contract interactions.
@@ -37,11 +37,11 @@ pub trait Tracing<T: Config> {
///
/// # Arguments
///
/// * `code_hash` - The code hash of the contract being called.
/// * `contract_address` - The address of the contract that is about to be executed.
/// * `entry_point` - Describes whether the call is the constructor or a regular call.
/// * `input_data` - The raw input data of the call.
fn new_call_span(
code_hash: &CodeHash<T>,
contract_address: &T::AccountId,
entry_point: ExportedFunction,
input_data: &[u8],
) -> Self::CallSpan;
@@ -60,8 +60,12 @@ pub trait CallSpan {
impl<T: Config> Tracing<T> for () {
type CallSpan = ();
fn new_call_span(code_hash: &CodeHash<T>, entry_point: ExportedFunction, input_data: &[u8]) {
log::trace!(target: LOG_TARGET, "call {entry_point:?} hash: {code_hash:?}, input_data: {input_data:?}")
fn new_call_span(
contract_address: &T::AccountId,
entry_point: ExportedFunction,
input_data: &[u8],
) {
log::trace!(target: LOG_TARGET, "call {entry_point:?} account: {contract_address:?}, input_data: {input_data:?}")
}
}
@@ -70,3 +74,37 @@ impl CallSpan for () {
log::trace!(target: LOG_TARGET, "call result {output:?}")
}
}
/// Provides an interface for intercepting contract calls.
pub trait CallInterceptor<T: Config> {
/// Allows to intercept contract calls and decide whether they should be executed or not.
/// If the call is intercepted, the mocked result of the call is returned.
///
/// # Arguments
///
/// * `contract_address` - The address of the contract that is about to be executed.
/// * `entry_point` - Describes whether the call is the constructor or a regular call.
/// * `input_data` - The raw input data of the call.
///
/// # Expected behavior
///
/// This method should return:
/// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call
/// is returned.
/// * `None` - otherwise, i.e. the call should be executed normally.
fn intercept_call(
contract_address: &T::AccountId,
entry_point: &ExportedFunction,
input_data: &[u8],
) -> Option<ExecResult>;
}
impl<T: Config> CallInterceptor<T> for () {
fn intercept_call(
_contract_address: &T::AccountId,
_entry_point: &ExportedFunction,
_input_data: &[u8],
) -> Option<ExecResult> {
None
}
}