-
Notifications
You must be signed in to change notification settings - Fork 699
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
Enable mocking contracts #1331
Conversation
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:
|
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. |
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?, | ||
}; |
There was a problem hiding this comment.
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 })?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this 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
* 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) ...
* 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) ...
# 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)
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
* reinitialize bridge subcommand * PolkadotToKusama in reinit-bridge
Description
This PR introduces two changes:
Tracing
trait has been modified to accept contract address instead of code hash (seems to be way more convenient)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 returnExecResult
insteadNote: one might try merging
before_call
andintercept_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
T
required)