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

Add READ_ONLY flag to contract call function #4418

Merged
merged 33 commits into from
Jun 4, 2024
Merged

Conversation

smiasojed
Copy link
Contributor

@smiasojed smiasojed commented May 8, 2024

This PR implements the READ_ONLY flag to be used as a Callflag in the call function.
The flag indicates that the callee is restricted from modifying the state during call execution.
It is equivalent to Ethereum's STATICCALL.

@smiasojed
Copy link
Contributor Author

bot bench substrate-pallet --pallet=pallet_contracts

@command-bot
Copy link

command-bot bot commented May 8, 2024

@smiasojed https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6163849 was started for your command "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts. Check out https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/pipelines?page=1&scope=all&username=group_605_bot to know what else is being executed currently.

Comment bot cancel 4-e1cd379c-2ad8-4462-ad01-76ff89dbf800 to cancel this command or bot cancel to cancel all commands in this pull request.

@smiasojed smiasojed added the T7-smart_contracts This PR/Issue is related to smart contracts. label May 8, 2024
@command-bot
Copy link

command-bot bot commented May 8, 2024

@smiasojed Command "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts has finished. Result: https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6163849 has finished. If any artifacts were generated, you can download them from https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6163849/artifacts/download.

@smiasojed smiasojed force-pushed the sm/contracts-staticcall branch from 315819c to 5278c7a Compare May 9, 2024 07:57
@smiasojed
Copy link
Contributor Author

bot bench substrate-pallet --pallet=pallet_contracts

@command-bot
Copy link

command-bot bot commented May 9, 2024

@smiasojed "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts (https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6165184) was cancelled in #4418 (comment)

@smiasojed
Copy link
Contributor Author

bot cancel 5-a1be7227-1f82-4a82-9891-dfc753222de3

@command-bot
Copy link

command-bot bot commented May 9, 2024

@smiasojed Command "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts has finished. Result: https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6165184 has finished. If any artifacts were generated, you can download them from https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6165184/artifacts/download.

@smiasojed
Copy link
Contributor Author

smiasojed commented May 9, 2024

@athei I assumed that chain_extension calls the API over the Ext trait, so read-only protection in the Ext implementation is good enough, please comment
Another option is to extend Ext trait with fn is_read_only(&self) -> bool so the chain_extension is aware of the read_only context.

@smiasojed
Copy link
Contributor Author

bot bench substrate-pallet --pallet=pallet_contracts

@command-bot
Copy link

command-bot bot commented May 9, 2024

@smiasojed https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6168456 was started for your command "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts. Check out https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/pipelines?page=1&scope=all&username=group_605_bot to know what else is being executed currently.

Comment bot cancel 1-461002b6-0e9a-417d-86f9-dad7ecaae79a to cancel this command or bot cancel to cancel all commands in this pull request.

…=dev --target_dir=substrate --pallet=pallet_contracts
@command-bot
Copy link

command-bot bot commented May 9, 2024

@smiasojed Command "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts has finished. Result: https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6168456 has finished. If any artifacts were generated, you can download them from https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6168456/artifacts/download.

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.

Some further ideas:

  1. Maybe move the checks from Ext to Runtime. I think it is easier to understand because the checks are per host function and that way we error out as early as possible. You could even implement this as attribute on the macro making it declarative. Having checks sprinkled around Ext seems not the best approach.

  2. I think there is no difference in how reentrancy and read_only is inherited. Can you unify them in one bit field? You may need to invert allow_reentrancy to make this happen.

substrate/frame/contracts/src/exec.rs Outdated Show resolved Hide resolved
@athei
Copy link
Member

athei commented May 10, 2024

@athei I assumed that chain_extension calls the API over the Ext trait, so read-only protection in the Ext implementation is good enough, please comment Another option is to extend Ext trait with fn is_read_only(&self) -> bool so the chain_extension is aware of the read_only context.

Yes it needs to be exposed on the Ext and chain extensions need to check it. With my comment above you need to expose it anyways if we move checks to the Runtime.

@smiasojed smiasojed marked this pull request as ready for review May 15, 2024 20:28
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.

Another detail if we want to mimic EVM STATICCALL semantics in full: STATICCALLs to accounts without code (e.g. call with the static flag set and no value) should succeed.

Note that an account with no code will return success as true (1).

(To clarify, EVM does balance transfers via calls to EOA. So in general, calls to EOA don't fail)

@athei
Copy link
Member

athei commented May 24, 2024

This is a general difference we have to Ethereum that is independent of STATICCCAL. We don't allow balance transfers to plain account via seal_call. We need to add this behaviour in order to support revive. But not on this PR and probably only after the fork since it is not needed for ink!.

@pgherveou
Copy link
Contributor

@smiasojed sorry for the new merge conflicts 😅
Let me know when this is ready for a final review

@smiasojed
Copy link
Contributor Author

@smiasojed sorry for the new merge conflicts 😅 Let me know when this is ready for a final review

@pgherveou conflicts resolved

Copy link
Contributor

@pgherveou pgherveou left a comment

Choose a reason for hiding this comment

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

lgtm

@@ -2125,7 +2134,15 @@ mod tests {
let value = Default::default();
let recurse_ch = MockLoader::insert(Call, |ctx, _| {
// Try to call into yourself.
let r = ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), BOB, 0, vec![], true);
let r = ctx.ext.call(
Copy link
Contributor

Choose a reason for hiding this comment

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

all these diffs added every time we add a parameter are a bit annoying,
when we get a chance, we should add builder, like we did in test_utils/builder.rs,

@smiasojed
Copy link
Contributor Author

bot bench substrate-pallet --pallet=pallet_contracts

@command-bot
Copy link

command-bot bot commented May 28, 2024

@smiasojed https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6328539 was started for your command "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts. Check out https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/pipelines?page=1&scope=all&username=group_605_bot to know what else is being executed currently.

Comment bot cancel 2-edb30dc9-0c17-49be-bbbc-50a7061cbb08 to cancel this command or bot cancel to cancel all commands in this pull request.

@command-bot
Copy link

command-bot bot commented May 28, 2024

@smiasojed Command "$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts has finished. Result: https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6328539 has finished. If any artifacts were generated, you can download them from https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/6328539/artifacts/download.

Comment on lines +227 to +235

if mutating {
let stmt = syn::parse_quote! {
if ctx.ext().is_read_only() {
return Err(Error::<E::T>::StateChangeDenied.into());
}
};
item.block.stmts.insert(0, stmt);
}
Copy link
Member

@athei athei May 29, 2024

Choose a reason for hiding this comment

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

As stated before: We can't return that early here. This codes runs before we charge the gas for the host function call invocation itself. This has to be charged even if no work has been performed. You have to move down this statement after:

// Charge gas for host function execution.
__caller__.data_mut().charge_gas(crate::wasm::RuntimeCosts::HostFn)
.map_err(TrapReason::from)
.map_err(#into_host)?;

Copy link
Contributor Author

@smiasojed smiasojed May 29, 2024

Choose a reason for hiding this comment

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

I agree, but my understanding is that the gas charge charge_gas(crate::wasm::RuntimeCosts::HostFn) is added before function body (mutating check is added to the function body)
Expanded:

                                    __caller__
                                        .data_mut()
                                        .charge_gas(crate::wasm::RuntimeCosts::HostFn)
                                        .map_err(TrapReason::from)
                                        .map_err(|reason| { ::wasmi::core::Trap::from(reason) })?;
                                    let mut func = || -> Result<(), TrapReason> {
                                        let (memory, ctx) = __caller__
                                            .data()
                                            .memory()
                                            .expect("Memory must be set when setting up host data; qed")
                                            .data_and_store_mut(&mut __caller__);
                                        let result = {
                                            if ctx.ext().is_read_only() {
                                                return Err(Error::<E::T>::StateChangeDenied.into());
                                            }
                                            ctx.set_storage(
                                                    memory,
                                                    KeyType::Fix,
                                                    key_ptr,
                                                    value_ptr,
                                                    value_len,
                                                )
                                                .map(|_| ())
                                        };

Copy link
Member

Choose a reason for hiding this comment

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

Ahh yes. Can you add a test that makes sure this weight is charged when erroring out early?

Copy link
Contributor Author

@smiasojed smiasojed May 29, 2024

Choose a reason for hiding this comment

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

I tried to test it using fixture:

pub extern "C" fn call() {
	input!(n: u32, );

	match n {
		0 => unsafe {core::ptr::read_volatile(core::ptr::null())},
		_ => api::set_storage(&[1u8; 32], &[1u8; 1]),
	}
}

The fixture is called with n equal to 0 and 1. Both calls result in a trap, but the difference is more significant than just the host function call cost because (I assume) the WASMI execution time differs for both cases. The difference is the host function call cost plus 15%.
HostFn ref_time is 72994

Copy link
Member

Choose a reason for hiding this comment

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

You can call the tokens function on the gas meter to check which tokens were charged.

Copy link
Contributor Author

@smiasojed smiasojed May 31, 2024

Choose a reason for hiding this comment

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

As discussed in priv, we do not have access to gas_meter in our tests. We could add such functionality to our tests to trace gas metering more deeply during testing. This will be implemented as a separate PR.

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.

You have to bump the ApiVersion and then we are good to go.

@athei athei added this pull request to the merge queue Jun 4, 2024
Merged via the queue into master with commit 3e84164 Jun 4, 2024
156 of 158 checks passed
@athei athei deleted the sm/contracts-staticcall branch June 4, 2024 08:23
ordian added a commit that referenced this pull request Jun 4, 2024
* master:
  Add READ_ONLY flag to contract call function  (#4418)
  Format the README.md files (#4688)
  Typos in template README (#4687)
  [ci] Increase timeout for check-runtime-migration workflow (#4674)
hitchhooker pushed a commit to ibp-network/polkadot-sdk that referenced this pull request Jun 5, 2024
This PR implements the `READ_ONLY` flag to be used as a `Callflag` in
the `call` function.
The flag indicates that the callee is restricted from modifying the
state during call execution.
It is equivalent to Ethereum's
[STATICCALL](https://eips.ethereum.org/EIPS/eip-214).

---------

Co-authored-by: command-bot <>
Co-authored-by: Andrew Jones <[email protected]>
Co-authored-by: Alexander Theißen <[email protected]>
TarekkMA pushed a commit to moonbeam-foundation/polkadot-sdk that referenced this pull request Aug 2, 2024
This PR implements the `READ_ONLY` flag to be used as a `Callflag` in
the `call` function.
The flag indicates that the callee is restricted from modifying the
state during call execution.
It is equivalent to Ethereum's
[STATICCALL](https://eips.ethereum.org/EIPS/eip-214).

---------

Co-authored-by: command-bot <>
Co-authored-by: Andrew Jones <[email protected]>
Co-authored-by: Alexander Theißen <[email protected]>
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.

6 participants