Skip to content

Commit

Permalink
Add READ_ONLY flag to contract call function (#4418)
Browse files Browse the repository at this point in the history
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 <ascjones@gmail.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
  • Loading branch information
3 people authored Jun 4, 2024
1 parent 09de7f1 commit 3e84164
Show file tree
Hide file tree
Showing 12 changed files with 808 additions and 457 deletions.
19 changes: 19 additions & 0 deletions prdoc/pr_4418.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "[pallet_contracts] Add `READ_ONLY` flag to contract `call` function"

doc:
- audience: Runtime User
description: |
This PR implements the `READ_ONLY` flag to be used as a `Callflag` in the contract `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).

crates:
- name: pallet-contracts
bump: minor
- name: pallet-contracts-uapi
bump: minor
- name: pallet-contracts-proc-macro
bump: minor
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! This fixture calls the account_id with the flags and value.
#![no_std]
#![no_main]

use common::input;
use uapi::{HostFn, HostFnImpl as api};

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
input!(
256,
callee_addr: [u8; 32],
flags: u32,
value: u64,
forwarded_input: [u8],
);

api::call_v2(
uapi::CallFlags::from_bits(flags).unwrap(),
callee_addr,
0u64, // How much ref_time to devote for the execution. 0 = all.
0u64, // How much proof_size to devote for the execution. 0 = all.
None, // No deposit limit.
&value.to_le_bytes(), // Value transferred to the contract.
forwarded_input,
None,
)
.unwrap();
}
50 changes: 50 additions & 0 deletions substrate/frame/contracts/fixtures/contracts/read_only_call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This fixture tests if read-only call works as expected.
#![no_std]
#![no_main]

use common::input;
use uapi::{HostFn, HostFnImpl as api};

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
input!(
256,
callee_addr: [u8; 32],
callee_input: [u8],
);

// Call the callee
api::call_v2(
uapi::CallFlags::READ_ONLY,
callee_addr,
0u64, // How much ref_time to devote for the execution. 0 = all.
0u64, // How much proof_size to devote for the execution. 0 = all.
None, // No deposit limit.
&0u64.to_le_bytes(), // Value transferred to the contract.
callee_input,
None,
)
.unwrap();
}
19 changes: 18 additions & 1 deletion substrate/frame/contracts/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,15 @@ impl HostFn {

// process attributes
let msg =
"Only #[version(<u8>)], #[unstable], #[prefixed_alias], #[cfg] and #[deprecated] attributes are allowed.";
"Only #[version(<u8>)], #[unstable], #[prefixed_alias], #[cfg], #[mutating] and #[deprecated] attributes are allowed.";
let span = item.span();
let mut attrs = item.attrs.clone();
attrs.retain(|a| !a.path().is_ident("doc"));
let mut maybe_version = None;
let mut is_stable = true;
let mut alias_to = None;
let mut not_deprecated = true;
let mut mutating = false;
let mut cfg = None;
while let Some(attr) = attrs.pop() {
let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string();
Expand Down Expand Up @@ -208,6 +209,12 @@ impl HostFn {
}
not_deprecated = false;
},
"mutating" => {
if mutating {
return Err(err(span, "#[mutating] can only be specified once"))
}
mutating = true;
},
"cfg" => {
if cfg.is_some() {
return Err(err(span, "#[cfg] can only be specified once"))
Expand All @@ -217,6 +224,16 @@ impl HostFn {
id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))),
}
}

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);
}

let name = item.sig.ident.to_string();

if !(is_stable || not_deprecated) {
Expand Down
6 changes: 6 additions & 0 deletions substrate/frame/contracts/src/chain_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ pub trait ChainExtension<C: Config> {
/// In case of `Err` the contract execution is immediately suspended and the passed error
/// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit
/// behaviour.
///
/// # Note
///
/// The [`Self::call`] can be invoked within a read-only context, where any state-changing calls
/// are disallowed. This information can be obtained using `env.ext().is_read_only()`. It is
/// crucial for the implementer to handle this scenario appropriately.
fn call<E: Ext<T = C>>(&mut self, env: Environment<E, InitState>) -> Result<RetVal>;

/// Determines whether chain extensions are enabled for this chain.
Expand Down
Loading

0 comments on commit 3e84164

Please sign in to comment.