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

Reduce gas/storage limits in nested calls #6890

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

rockbmb
Copy link
Contributor

@rockbmb rockbmb commented Dec 13, 2024

Closes #6846 .

@rockbmb rockbmb added T2-pallets This PR/Issue is related to a particular pallet. T7-smart_contracts This PR/Issue is related to smart contracts. T10-tests This PR/Issue is related to tests. labels Dec 13, 2024
@rockbmb rockbmb self-assigned this Dec 13, 2024
@rockbmb rockbmb force-pushed the evm-contracts-limit-nested-calls-gas branch 2 times, most recently from 376e944 to 4832380 Compare December 17, 2024 02:17
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`
@rockbmb rockbmb force-pushed the evm-contracts-limit-nested-calls-gas branch from ed1f312 to 8e66f50 Compare December 17, 2024 19:15
@rockbmb rockbmb marked this pull request as ready for review December 17, 2024 19:31
@athei athei requested review from athei and xermicus December 17, 2024 20:08
Copy link
Member

@xermicus xermicus left a 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.

.min(self.gas_left);
self.gas_left -= amount;
GasMeter::new(amount)
// The reduction to 63/64 is to emulate EIP-150
Copy link
Member

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

substrate/frame/revive/src/storage/meter.rs Outdated Show resolved Hide resolved
debug_assert!(matches!(self.contract_state(), ContractState::Alive));

// Limit gas for nested call, per EIP-150.
Copy link
Member

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`
@rockbmb
Copy link
Contributor Author

rockbmb commented Dec 19, 2024

@xermicus the meters in frame::contracts::{gas, storage::meter} also need to reflect this, correct?

@athei
Copy link
Member

athei commented Dec 19, 2024

Sorry if this wasn't clear: Only pallet_revive and its supporting crates should be changed. pallet_contracts should remain untouched. It does't receive any updates beyond critical bug fixes anymore. Can you roll back the changes there? Will have a deeper look then.

@rockbmb
Copy link
Contributor Author

rockbmb commented Dec 19, 2024

@athei frame/contracts changes have been reverted, thanks for explaining this.

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, cargo test --package pallet-revive:0.1.0 --lib --all-features yields:

failures:
    benchmarking::benchmarks::bench_seal_instantiate
    tests::call_diverging_out_len_works
    tests::create1_with_value_works
    tests::delegate_call
    tests::deploy_and_call_other_contract
    tests::deposit_limit_in_nested_calls
    tests::deposit_limit_in_nested_instantiate
    tests::destroy_contract_and_transfer_funds
    tests::failed_deposit_charge_should_roll_back_call
    tests::gas_estimation_call_runtime
    tests::gas_estimation_for_subcalls
    tests::return_data_api_works
    tests::skip_transfer_works
    tests::storage_deposit_callee_works
    tests::test_debug::debugging_works

The contracts the above tests rely on have removed usages of 0 as "use all", so their failures are somewhere else.

@rockbmb
Copy link
Contributor Author

rockbmb commented Dec 21, 2024

14 unit tests are still failing, among them

  1. tests::deposit_limit_in_nested_calls, and
  2. tests::deposit_limit_in_nested_instantiate

these 2 tests fail because of a ContractReverted error where StorageDepositLimitExhausted was expected.

The remaining tests fail with ContractTrapped when assert_oking the result of CallBuilder::build.
E.g. for tests::delegate_call:

assert_ok!(builder::call(caller_addr) <-- assert fails!
	.value(1337)
	.data((callee_addr, 0u64, 0u64).encode())
	.build());

⬆️ 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.

Copy link
Member

@athei athei left a 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.

substrate/frame/revive/src/exec.rs Outdated Show resolved Hide resolved
substrate/frame/revive/src/exec.rs Show resolved Hide resolved
substrate/frame/revive/src/gas.rs Outdated Show resolved Hide resolved
substrate/frame/revive/src/storage/meter.rs Outdated Show resolved Hide resolved
substrate/frame/revive/src/storage/meter.rs Outdated Show resolved Hide resolved
@athei
Copy link
Member

athei commented Jan 2, 2025

these 2 tests fail because of a ContractReverted error where StorageDepositLimitExhausted was expected.

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 StorageDepositLimitExhausted it will just be returned as an error code to the caller. This is so that the caller can decide how to handle errors in sub calls rather than just reverting.

The change you made by removing enforce_subcall_limit can leadd to the situation that we fail in the sub calls context when running out of storage deposit. At least when we were passing 0. Before we delayed the check to the last frame returning and failed in the callers context. The caller probably just reverts on non-zero return codes from the sub calls. This is why you get the generic ContractReverted instead of the more detailed StorageDepositLimitExhausted.

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 ContractReverted error.

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.
@rockbmb
Copy link
Contributor Author

rockbmb commented Jan 3, 2025

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 ContractReverted error.

Thanks for clarifying this - in acfcf66 and e0c0845, both tests that threw ContractReverted are corrected.

The remaining ones throw ContractTrapped - as you said, these tests may need to be changed.

substrate/frame/revive/src/exec.rs Outdated Show resolved Hide resolved
substrate/frame/revive/src/storage/meter.rs Outdated Show resolved Hide resolved
@rockbmb rockbmb force-pushed the evm-contracts-limit-nested-calls-gas branch from 1844efa to b090e5f Compare January 6, 2025 14:26
@rockbmb
Copy link
Contributor Author

rockbmb commented Jan 6, 2025

Apologies for the force-push - I made a mistake in a commit prior to the master merge.

A limit of `0` no longer signifies "no limit", and must thus be changed
to `u64::MAX`.
@rockbmb
Copy link
Contributor Author

rockbmb commented Jan 6, 2025

/cmd fmt

Copy link

github-actions bot commented Jan 6, 2025

Command "fmt" has started 🚀 See logs here

Copy link

github-actions bot commented Jan 6, 2025

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.
@rockbmb
Copy link
Contributor Author

rockbmb commented Jan 6, 2025

The only tests that still need to be addressed:

failures:
    tests::gas_estimation_for_subcalls
    tests::skip_transfer_works

substrate/frame/revive/src/wasm/runtime.rs Outdated Show resolved Hide resolved
substrate/frame/revive/src/wasm/runtime.rs Outdated Show resolved Hide resolved
@rockbmb
Copy link
Contributor Author

rockbmb commented Jan 7, 2025

@athei One question:

What is the meaning of value: 0u32.into()?
I.e. after the semantic change in the meaning of 0 introduced by this PR, should its default still be 0?

This is causing tests::gas_estimation_for_subcalls to fail when creating the storage meter for Pallet::bare_call by creating a meter with 0 limit, eventually leading to ContractTrapped.

@athei
Copy link
Member

athei commented Jan 8, 2025

@athei One question:

What is the meaning of value: 0u32.into()? I.e. after the semantic change in the meaning of 0 introduced by this PR, should its default still be 0?

This is causing tests::gas_estimation_for_subcalls to fail when creating the storage meter for Pallet::bare_call by creating a meter with 0 limit, eventually leading to ContractTrapped.

The line you quoted is value which is the amount of balance sent to the callee as part of the call. This is unrelated to the deposit limit you changed. Its behavior should be unaffected by this PR.

There, its meaning was "use all available gas", which needed to be
updated to `Weight::MAX`.
///
/// Deposit limits are `U256`, but balances are represented as `u64`.
/// To represent no deposit limits on an operation, this should be used.
pub const U256_MAX: [u8; 32] = u64_to_u256_bytes(u64::MAX);
Copy link
Member

Choose a reason for hiding this comment

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

Should be the same, right?

Suggested change
pub const U256_MAX: [u8; 32] = u64_to_u256_bytes(u64::MAX);
pub const U256_MAX: [u8; 32] = [1; 32];

Weight::zero(),
U256::zero(),
Weight::MAX,
U256::from(u64::MAX),
Copy link
Member

Choose a reason for hiding this comment

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

Same for the other occurrences.

Suggested change
U256::from(u64::MAX),
U256::MAX,

@paritytech-workflow-stopper
Copy link

All GitHub workflows were cancelled due to failure one of the required jobs.
Failed workflow url: https://github.com/paritytech/polkadot-sdk/actions/runs/12673009936
Failed job name: quick-benchmarks-omni

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T2-pallets This PR/Issue is related to a particular pallet. T7-smart_contracts This PR/Issue is related to smart contracts. T10-tests This PR/Issue is related to tests.
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

Limit resources to 63/64 on sub calls and remove 0 as special case for "take all"
3 participants