Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable mocking contracts #1331

Merged
merged 20 commits into from
Sep 29, 2023

Conversation

pmikolajczyk41
Copy link
Contributor

@pmikolajczyk41 pmikolajczyk41 commented Aug 31, 2023

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 (inkdevhub/drink#33)

Checklist

  • My PR includes a detailed description as outlined in the "Description" section above
  • My PR follows the labeling requirements of this project (at minimum one label for T required)
  • I have made corresponding changes to the documentation (if applicable)
  • I have added tests that prove my fix is effective or that my feature works (if applicable)

@pgherveou
Copy link
Contributor

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

If you control the full environment with Drink!, wouldn't you be able to move this mocking logic outside of pallet-contract, for example by returning a mocked contract from storage when it's fetched by the runtime

@pmikolajczyk41
Copy link
Contributor Author

If you control the full environment with Drink!, wouldn't you be able to move this mocking logic outside of pallet-contract, for example by returning a mocked contract from storage when it's fetched by the runtime

I guess that's doable, but honestly I don't have an idea now how to intervene into storage reading in test externalities. Even if I could, there are some other problems:

  • pallet contracts used to do a few things regarding contract's wasm blob, including instrumentation and some other stuff, including contract ownership; therefore, handling all of these on drink! side will be hellish task
  • keeping it in pallet-contracts let's you mock a subset of contract's methods, or even mock contract method for a particular set of parameters

@deuszx
Copy link

deuszx commented Aug 31, 2023

And, most importantly IMHO from the POV of a smart contract developer (and that's the audience of this feature) they don't need to write a "mocked contract" at all! In your proposal they'd have to and in that case we don't need this feature at all - just install the modified contract and use that instead.

Comment on lines 912 to 922
let call_span = T::Debug::new_call_span(contract_address, entry_point, &input_data);
let interception_result =
T::Debug::intercept_call(contract_address, entry_point, &input_data);
let output = match interception_result {
// Call into the Wasm blob.
CallInterceptorResult::Proceed => executable
.execute(self, &entry_point, input_data)
.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?,
// Return the output of the interceptor.
CallInterceptorResult::Stop(output) => output?,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can move the details of intercept_call to the trait implementation, so we don't leak the struct that could then be internal to the implementation.

We could move that behind a feature-flag like this

let output = if cfg!(feature = "unsafe-debug") {
    T::Debug::wrap_execute(self, contract_address, &entry_point, input_data)
} else {
    executable.execute(self, &entry_point, input_data)
}.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?

or maybe simply

let output = T::Debug::wrap_execute(self, contract_address, &entry_point, input_data)
  .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move that behind a feature-flag like this

we have removed the feature flag, haven't we?

we don't leak the struct

what if we return just Result<(), ExecResult> instead of the current new enum type? same semantics, no new types

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if we return just Result<(), ExecResult> instead of the current new enum type? same semantics, no new types

Or Option<ExecResult> which is less confusing to the implementor of the trait but would require a bit more work here to handle it properly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have removed the feature flag, haven't we?

We can always re-introduce it. For tracing we needed to have basic traces regardless of the environment, so we could get away without introducing a feature flag. Intercepting calls seems more like a dev only feature, so I think it could make sense to have that gated behind a feature flag.

Moving it all behind a T::Debug::wrap_execute that would just call the actual code with the default () Debug trait might be good alternative as well. @athei any thought on this?

what if we return just Result<(), ExecResult> instead of the current new enum type? same semantics, no new types

I think that's what I am suggesting with the code snippet above.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's what I am suggesting with the code snippet above.

That's not how we saw it. You suggested (assuming it's the 2nd snippet):

let output = T::Debug::wrap_execute(self, contract_address, &entry_point, input_data)
  .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?

which looked like the wrap_execute should also call execute?

I think we can leave the T::Debug::intercept_call as-is but change the return type.

if we agree to the general idea I think we can pin down the details (method name and return type).

Copy link
Contributor

@pgherveou pgherveou Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which looked like the wrap_execute should also call execute?

Yes right

I think we can leave the T::Debug::intercept_call as-is but change the return type.

So you would do something like

if let Some(result) = T::Debug::intercept_call(contract_address, entry_point, &input_data) {
  result
} else {
// current logic
}

Yes I guess we can start with this

Copy link

@deuszx deuszx Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Or slightly more cleanly IMHO

if let Some(output) = T::Debug::intercept_call(contract_address, entry_point, &input_data) {
  output?
}

// business as usual

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so all in all I've removed the new CallInterceptorResult enum in favor of Option... does it meet the consensus above?

substrate/frame/contracts/src/debug.rs Show resolved Hide resolved
substrate/frame/contracts/src/exec.rs Outdated Show resolved Hide resolved
substrate/frame/contracts/src/tests/test_debug.rs Outdated Show resolved Hide resolved
@pmikolajczyk41 pmikolajczyk41 requested a review from a team September 25, 2023 15:23
@pgherveou pgherveou added the T7-smart_contracts This PR/Issue is related to smart contracts. label Sep 28, 2023
Copy link
Contributor

@agryaznov agryaznov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice feature! just a little nit-picking

substrate/frame/contracts/src/tests/test_debug.rs Outdated Show resolved Hide resolved
substrate/frame/contracts/src/debug.rs Outdated Show resolved Hide resolved
substrate/frame/contracts/src/debug.rs Outdated Show resolved Hide resolved
substrate/frame/contracts/src/exec.rs Outdated Show resolved Hide resolved
substrate/frame/contracts/src/tests/test_debug.rs Outdated Show resolved Hide resolved
@pgherveou pgherveou merged commit d8d90a8 into paritytech:master Sep 29, 2023
7 of 8 checks passed
@pmikolajczyk41 pmikolajczyk41 deleted the mock-contracts branch September 29, 2023 13:39
ordian added a commit that referenced this pull request Oct 3, 2023
* master: (24 commits)
  Init System Parachain storage versions and add migration check jobs to CI (#1344)
  no-bound derives: Use absolute path for `core` (#1763)
  migrate alliance, fast-unstake and bags list to use derive-impl (#1636)
  Tvl pool staking (#1322)
  improve service error (#1734)
  frame-support: `RuntimeDebug\Eq\PartialEq` impls for `Imbalance` (#1717)
  Point documentation links to monorepo (#1741)
  [NPoS] Fix for Reward Deficit in the pool (#1255)
  Move import queue from `ChainSync` to `SyncingEngine` (#1736)
  Enable mocking contracts (#1331)
  Revert "fix(review-bot): pull secrets from `master` environment" (#1748)
  Remove kusama and polkadot runtime crates (#1731)
  Use `Extensions` to register offchain worker custom extensions (#1719)
  [RPC-Spec-V2] chainHead: use integer for block index and adjust RuntimeVersion JSON format (#1666)
  fix(review-bot): pull secrets from `master` environment (#1745)
  Fix `subkey inspect` output text padding (#1744)
  archive: Implement height, hashByHeight and call (#1582)
  rpc/client: Propagate `rpc_methods` method to reported methods (#1713)
  rococo-runtime: `RococoGenesisExt` removed (#1490)
  PVF: more filesystem sandboxing (#1373)
  ...
ordian added a commit that referenced this pull request Oct 10, 2023
* tsv-disabling-node-side: (24 commits)
  Init System Parachain storage versions and add migration check jobs to CI (#1344)
  no-bound derives: Use absolute path for `core` (#1763)
  migrate alliance, fast-unstake and bags list to use derive-impl (#1636)
  Tvl pool staking (#1322)
  improve service error (#1734)
  frame-support: `RuntimeDebug\Eq\PartialEq` impls for `Imbalance` (#1717)
  Point documentation links to monorepo (#1741)
  [NPoS] Fix for Reward Deficit in the pool (#1255)
  Move import queue from `ChainSync` to `SyncingEngine` (#1736)
  Enable mocking contracts (#1331)
  Revert "fix(review-bot): pull secrets from `master` environment" (#1748)
  Remove kusama and polkadot runtime crates (#1731)
  Use `Extensions` to register offchain worker custom extensions (#1719)
  [RPC-Spec-V2] chainHead: use integer for block index and adjust RuntimeVersion JSON format (#1666)
  fix(review-bot): pull secrets from `master` environment (#1745)
  Fix `subkey inspect` output text padding (#1744)
  archive: Implement height, hashByHeight and call (#1582)
  rpc/client: Propagate `rpc_methods` method to reported methods (#1713)
  rococo-runtime: `RococoGenesisExt` removed (#1490)
  PVF: more filesystem sandboxing (#1373)
  ...
bgallois pushed a commit to duniter/duniter-polkadot-sdk that referenced this pull request Mar 25, 2024
# 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 (inkdevhub/drink#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)
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 8, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 8, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 8, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 8, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 9, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 9, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 9, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 9, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 9, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 9, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 10, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
serban300 pushed a commit to serban300/polkadot-sdk that referenced this pull request Apr 10, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
bkchr pushed a commit that referenced this pull request Apr 10, 2024
* reinitialize bridge subcommand

* PolkadotToKusama in reinit-bridge
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T7-smart_contracts This PR/Issue is related to smart contracts.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants