-
Notifications
You must be signed in to change notification settings - Fork 761
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
Reduce gas/storage limits in nested calls #6890
base: master
Are you sure you want to change the base?
Conversation
376e944
to
4832380
Compare
Also, a limit of 0 when determining a nested call's metering limit would mean it was free to use all of the callee's resources. Now, a limit of 0 means that the nested call will have an empty storage meter.
In particular, this commit removes the usage of `0` as unlimited metering in the following tests: - `nonce` - `last_frame_output_works_on_instantiate` - `instantiation_from_contract` - `immutable_data_set_works_only_once` - `immutable_data_set_errors_with_empty_data` - `immutable_data_access_checks_work`
ed1f312
to
8e66f50
Compare
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.
The basic idea of the 63/64 rule looks correct to me but there are many failing tests. I left some nits.
substrate/frame/revive/src/gas.rs
Outdated
.min(self.gas_left); | ||
self.gas_left -= amount; | ||
GasMeter::new(amount) | ||
// The reduction to 63/64 is to emulate EIP-150 |
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.
This should be a doc-comment
debug_assert!(matches!(self.contract_state(), ContractState::Alive)); | ||
|
||
// Limit gas for nested call, per EIP-150. |
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.
This could go into the doc-comment (which is no no-longer correct I think)
This fixes, among other tests: * `tests::gas_consumed_is_linear_for_nested_calls` test * `tests::deposit_limit_in_nested_calls` * `tests::transient_storage_limit_in_call` * `tests::call_return_code` * `test::chain_extension_temp_storage_works` * `tests::origin_api_works` * `tests::read_only_call_works`
@xermicus the meters in |
Sorry if this wasn't clear: Only |
@athei Most of the previously failing tests were caused by the contract code of those tests using the now-removed '0 means "use all available gas"' semantic. After fixing this,
The contracts the above tests rely on have removed usages of 0 as "use all", so their failures are somewhere else. |
/bot fmt |
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.
Due to the changes here you can get rid of the fn enforce_subcall_limit()
and enum Nested
. They are no longer needed because we now always enforce the limit at the sub call boundry (calling fn enforce_limit
unconditionally).
Apart from a few other comments it looks pretty much like what I expect. Thanks.
...us/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs
Outdated
Show resolved
Hide resolved
If I raze
? |
The type stating is still useful for other functions than Just remove the variants of |
14 unit tests are still failing, among them these 2 tests fail because of a The remaining tests fail with
⬆️ is context in case you have any insights to share - the tests all failing for the same reason must be a clue to the underlying problem. I'll move fast so paritytech/revive#117 can continue. |
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.
Looks good. Just some nits.
Regarding the failing tests: Your code looks correct to me. However, it is a breaking change as it prevents sub calls from using all the remaining gas which was possible before. So I assume the tests rely in one way or another on this behavior and need to be changed.
/// | ||
/// We normally need to call this **once** for every call stack and not for every cross contract | ||
/// call. However, if a dedicated limit is specified for a sub-call, this needs to be called | ||
/// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is | ||
/// used. | ||
/// once the sub-call has returned. |
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.
Doc comment needs updating to reflect the new much simpler behavior: This has to be called every time a sub call returns. No special casing anymore for a 'dedicated limit'. We just removed this special casing.
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.
This comment has been changed on my tree, and no longer contains any reference to enforce_subcall_limit
.
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.
The rest of the comment is still stale:
/// We normally need to call this **once** for every call stack and not for every cross contract
/// call. However, if a dedicated limit is specified for a sub-call, this needs to be called
There is no special limit anymore. Also, we never call it once per call stack as stated here. We call it for every sub call now.
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 was wrong, thanks for clarifying.
This can be a bit finicky and requires some knowledge of how the error handling works in contracts to debug. The error you are asserting on here is what is returned from the root contract that is called from the transaction. However, when this contract calls into another contract and the callee encounters an error like The change you made by removing This is actually expected and nice that it is covered by test cases. You should change the tests to adapt to the new behavior. You can see that the caller contract also returns the sub calls return code in addition to reverting. So you can check this return code in your test and are not at the mercy of the generic |
Use `BareCallBuilder::bare_call` instead of `CallBuilder::call` to allow scrutiny of contract return code with `assert_return_code`, and disambiguate the cause of the `ContractReverted` error that `CallBuilder::build` would have raised.
Thanks for clarifying this - in The remaining ones throw |
substrate/frame/revive/src/exec.rs
Outdated
// If a special limit was set for the sub-call, we enforce it here. | ||
// The sub-call will be rolled back in case the limit is exhausted. |
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.
// If a special limit was set for the sub-call, we enforce it here. | |
// The sub-call will be rolled back in case the limit is exhausted. | |
// We are only charging the storage deposit at the end of every call stack. | |
// To make sure that no sub call uses more than it is allowed we need to | |
// manually enforce the limit here. |
@@ -126,7 +121,7 @@ pub struct RawMeter<T: Config, E, S: State + Default + Debug> { | |||
/// limited by the maximum call depth. | |||
charges: Vec<Charge<T>>, | |||
/// We store the nested state to determine if it has a special limit for sub-call. | |||
nested: S, | |||
_nested: PhantomData<S>, | |||
/// Type parameter only used in impls. | |||
_phantom: PhantomData<E>, |
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.
You can remove the nested field.
_phantom: PhantomData<E>, | |
_phantom: PhantomData<(E, S)>, |
1844efa
to
b090e5f
Compare
Apologies for the force-push - I made a mistake in a commit prior to the |
A limit of `0` no longer signifies "no limit", and must thus be changed to `u64::MAX`.
/cmd fmt |
Command "fmt" has started 🚀 See logs here |
Command "fmt" has finished ✅ See logs here |
A deposit limit of 0 no longer is a special case, and is treated as no limit being available for use in a benchmarking function.
The only tests that still need to be addressed:
|
let deposit_limit = if deposit_ptr == SENTINEL { | ||
U256::from(u64::MAX) | ||
} else { | ||
memory.read_u256(deposit_ptr)? | ||
}; |
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.
let deposit_limit = if deposit_ptr == SENTINEL { | |
U256::from(u64::MAX) | |
} else { | |
memory.read_u256(deposit_ptr)? | |
}; | |
memory.read_u256(deposit_ptr)? |
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 don't want any special handling here anymore.
let deposit_limit: U256 = if deposit_ptr == SENTINEL { | ||
U256::from(u64::MAX) | ||
} else { | ||
memory.read_u256(deposit_ptr)? | ||
}; |
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.
let deposit_limit: U256 = if deposit_ptr == SENTINEL { | |
U256::from(u64::MAX) | |
} else { | |
memory.read_u256(deposit_ptr)? | |
}; | |
memory.read_u256(deposit_ptr)? |
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.
This raises OutOfBounds
errors in many tests (it replaced the last 2 tests' failure ContractTrapped
failure, which is progress).
Do you have an idea of what might be causing 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.
The test fixtures need to be adapted to always pass a valid pointer. SENTINEL
is used to indicate a None
so to say.
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.
For example, in substrate/frame/revive/fixtures/contracts/call.rs
, the deposit limit is None
- this is was we want, right
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. You need to change the wrapper in pallet-fixtures-uapi
. It no longer takes an Option
since the value is no longer optional. You then pass in U256::MAX
where None
was passes before.
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.
Where there is U256::MAX
, do you mean &[u8::MAX; 32]
, since in trait HostFn
one has deposit_limit: &[u8; 32],
?
Furthermore, instead of &[u8::MAX; 32]
, won't we want something like &[u8::MAX; 8].concat([0u8; 24]
? Equivalent to U256::from(u64::MAX)
.
Using &[u8::MAX; 32]
will raise Error::BalanceConversionFailed
when the value is read from memory and converted into a Balance<T>
.
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 opted for (something similar to) &[0xFF; 8].concat([0u8; 24]
to solve this issue.
All GitHub workflows were cancelled due to failure one of the required jobs. |
@athei One question: What is the meaning of This is causing |
Closes #6846 .