diff --git a/substrate/frame/contracts/fixtures/contracts/xcm_query.rs b/substrate/frame/contracts/fixtures/contracts/xcm_query.rs new file mode 100644 index 000000000000..2d24e9f8af71 --- /dev/null +++ b/substrate/frame/contracts/fixtures/contracts/xcm_query.rs @@ -0,0 +1,37 @@ +// 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. + +#![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!(512, timeout: [u8; 8], match_querier: [u8],); + let mut query_id = [0u8; 8]; + + #[allow(deprecated)] + api::xcm_query(timeout, match_querier, &mut query_id).unwrap(); + api::return_value(uapi::ReturnFlags::empty(), &query_id); +} diff --git a/substrate/frame/contracts/fixtures/contracts/xcm_take_response.rs b/substrate/frame/contracts/fixtures/contracts/xcm_take_response.rs new file mode 100644 index 000000000000..4b0740b9e36d --- /dev/null +++ b/substrate/frame/contracts/fixtures/contracts/xcm_take_response.rs @@ -0,0 +1,38 @@ +// 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. + +#![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!(query_id: [u8; 8],); + let mut response_status = [0u8; 100]; + + #[allow(deprecated)] + api::xcm_take_response(query_id, &mut response_status).unwrap(); + + api::return_value(uapi::ReturnFlags::empty(), &response_status); +} diff --git a/substrate/frame/contracts/mock-network/src/tests.rs b/substrate/frame/contracts/mock-network/src/tests.rs index d22221fe8ee0..3c345e3e74f6 100644 --- a/substrate/frame/contracts/mock-network/src/tests.rs +++ b/substrate/frame/contracts/mock-network/src/tests.rs @@ -16,10 +16,11 @@ // limitations under the License. use crate::{ - parachain::{self, Runtime}, + parachain::{self, Runtime, RuntimeOrigin}, parachain_account_sovereign_account_id, primitives::{AccountId, CENTS}, - relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, + relay_chain, MockNet, ParaA, ParachainBalances, ParachainPalletXcm, Relay, ALICE, BOB, + INITIAL_BALANCE, }; use codec::{Decode, Encode}; use frame_support::{ @@ -27,14 +28,17 @@ use frame_support::{ pallet_prelude::Weight, traits::{fungibles::Mutate, Currency}, }; +use frame_system::pallet_prelude::BlockNumberFor; use pallet_balances::{BalanceLock, Reasons}; use pallet_contracts::{Code, CollectEvents, DebugInfo, Determinism}; use pallet_contracts_fixtures::compile_module; use pallet_contracts_uapi::ReturnErrorCode; use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; +use xcm_executor::traits::{QueryHandler, QueryResponseStatus}; use xcm_simulator::TestExt; type ParachainContracts = pallet_contracts::Pallet; +type QueryId = as QueryHandler>::QueryId; macro_rules! assert_return_code { ( $x:expr , $y:expr $(,)? ) => {{ @@ -276,3 +280,102 @@ fn test_xcm_send() { ); }); } + +#[test] +fn test_xcm_query() { + MockNet::reset(); + let contract_addr = instantiate_test_contract("xcm_query"); + + ParaA::execute_with(|| { + let match_querier = Location::from(AccountId32 { network: None, id: ALICE.into() }); + let match_querier = VersionedLocation::V4(match_querier); + let timeout: BlockNumberFor = 1u32.into(); + + // Invoke the contract to create an XCM query. + let exec = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + (timeout, match_querier).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + let mut data = &exec.result.unwrap().data[..]; + let query_id = QueryId::decode(&mut data).expect("Failed to decode message"); + + // Verify that the query exists and is pending. + let response = ::take_response(query_id); + let expected_response = QueryResponseStatus::Pending { timeout }; + assert_eq!(response, expected_response); + }); +} + +#[test] +fn test_xcm_take_response() { + MockNet::reset(); + let contract_addr = instantiate_test_contract("xcm_take_response"); + + ParaA::execute_with(|| { + let querier: Location = (Parent, AccountId32 { network: None, id: ALICE.into() }).into(); + let responder = Location::from(AccountId32 { + network: Some(NetworkId::ByGenesis([0u8; 32])), + id: ALICE.into(), + }); + + // Register a new query. + let query_id = ParachainPalletXcm::new_query(responder, 1u32.into(), querier.clone()); + + // Helper closure to call the contract to take the response. + let call = |query_id: QueryId| { + let exec = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + query_id.encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + QueryResponseStatus::>::decode( + &mut &exec.result.unwrap().data[..], + ) + .expect("Failed to decode message") + }; + + // Query is not yet answered. + assert_eq!(QueryResponseStatus::Pending { timeout: 1u32.into() }, call(query_id)); + + // Execute the XCM program that answers the query. + let message = Xcm(vec![QueryResponse { + query_id, + response: Response::ExecutionResult(None), + max_weight: Weight::zero(), + querier: Some(querier), + }]); + ParachainPalletXcm::execute( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedXcm::V4(message)), + Weight::from_parts(1_000_000_000, 1_000_000_000), + ) + .unwrap(); + + // First call returns the response. + assert_eq!( + QueryResponseStatus::Ready { + response: Response::ExecutionResult(None), + at: 1u32.into() + }, + call(query_id) + ); + + // Second call returns `NotFound`. (Query was already answered) + assert_eq!(QueryResponseStatus::NotFound, call(query_id)); + }) +} diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index f440c818166d..73a22cf184f0 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -2075,7 +2075,7 @@ pub mod env { ) -> Result { let str_len = str_len.min(DebugBufferVec::::bound() as u32); ctx.charge_gas(RuntimeCosts::DebugMessage(str_len))?; - if ctx.ext.append_debug_buffer("") { + if ctx.ext.debug_buffer_enabled() { let data = ctx.read_sandbox_memory(memory, str_ptr, str_len)?; if let Some(msg) = core::str::from_utf8(&data).ok() { ctx.ext.append_debug_buffer(msg); @@ -2171,7 +2171,7 @@ pub mod env { Ok(ReturnErrorCode::Success) }, Err(e) => { - if ctx.ext.append_debug_buffer("") { + if ctx.ext.debug_buffer_enabled() { ctx.ext.append_debug_buffer("seal0::xcm_send failed with: "); ctx.ext.append_debug_buffer(e.into()); }; @@ -2180,6 +2180,86 @@ pub mod env { } } + /// Create a new query, using the contract's address as the responder. + /// + /// # Parameters + /// + /// - `timeout_ptr`: the pointer into the linear memory where the timeout is placed. + /// - `match_querier_ptr`: the pointer into the linear memory where the match_querier is placed. + /// - `output_ptr`: the pointer into the linear memory where the + /// [`xcm_builder::QueryHandler::QueryId`] is placed. + /// + /// # Return Value + /// + /// Returns `ReturnCode::Success` when the query was successfully created. When the query + /// creation fails, `ReturnCode::XcmQueryFailed` is returned. + #[unstable] + fn xcm_query( + ctx: _, + memory: _, + timeout_ptr: u32, + match_querier_ptr: u32, + output_ptr: u32, + ) -> Result { + use frame_system::pallet_prelude::BlockNumberFor; + use xcm::VersionedLocation; + use xcm_builder::{QueryController, QueryControllerWeightInfo}; + + let timeout: BlockNumberFor = ctx.read_sandbox_memory_as(memory, timeout_ptr)?; + let match_querier: VersionedLocation = + ctx.read_sandbox_memory_as(memory, match_querier_ptr)?; + + let weight = <::Xcm as QueryController<_, _>>::WeightInfo::query(); + ctx.charge_gas(RuntimeCosts::CallRuntime(weight))?; + let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into(); + + match <::Xcm>::query(origin, timeout, match_querier) { + Ok(query_id) => { + ctx.write_sandbox_memory(memory, output_ptr, &query_id.encode())?; + Ok(ReturnErrorCode::Success) + }, + Err(e) => { + if ctx.ext.debug_buffer_enabled() { + ctx.ext.append_debug_buffer("call failed with: "); + ctx.ext.append_debug_buffer(e.into()); + }; + Ok(ReturnErrorCode::XcmQueryFailed) + }, + } + } + + /// Take an XCM response for the specified query. + /// + /// # Parameters + /// + /// - `query_id_ptr`: the pointer into the linear memory where the + /// [`xcm_builder::QueryHandler::QueryId`] is placed. + /// - `output_ptr`: the pointer into the linear memory where the response + /// [`xcm_builder::QueryResponseStatus`] is placed. + /// + /// # Return Value + /// + /// Returns `ReturnCode::Success` when successful. + #[unstable] + fn xcm_take_response( + ctx: _, + memory: _, + query_id_ptr: u32, + output_ptr: u32, + ) -> Result { + use xcm_builder::{QueryController, QueryControllerWeightInfo, QueryHandler}; + + let query_id: <::Xcm as QueryHandler>::QueryId = + ctx.read_sandbox_memory_as(memory, query_id_ptr)?; + + let weight = <::Xcm as QueryController<_, _>>::WeightInfo::take_response(); + ctx.charge_gas(RuntimeCosts::CallRuntime(weight))?; + + let response = <::Xcm>::take_response(query_id).encode(); + ctx.write_sandbox_memory(memory, output_ptr, &response)?; + Ok(ReturnErrorCode::Success) + } + /// Recovers the ECDSA public key from the given message hash and signature. /// See [`pallet_contracts_uapi::HostFn::ecdsa_recover`]. #[prefixed_alias] diff --git a/substrate/frame/contracts/uapi/src/host.rs b/substrate/frame/contracts/uapi/src/host.rs index c25be4479cef..0af56d55633f 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -790,7 +790,7 @@ pub trait HostFn { /// /// # Parameters /// - /// - `dest`: The XCM destination, should be decodable as [VersionedMultiLocation](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedMultiLocation.html), + /// - `dest`: The XCM destination, should be decodable as [VersionedLocation](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedLocation.html), /// traps otherwise. /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), /// traps otherwise. @@ -803,4 +803,34 @@ pub trait HostFn { note = "Unstable function. Behaviour can change without further notice. Use only for testing." )] fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; + + /// Create a new query, using the contract's address as the responder. + /// + /// # Parameters + /// + /// - `timeout_ptr`: The query timeout, should be decodable as a `BlockNumberFor`. + /// - `match_querier`: The match_querier should be decodable as [VersionedLocation](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedLocation.html), + /// - `output`: A reference to the output data buffer to write the + /// [`xcm_builder::QueryHandler::QueryId`]. + /// + /// # Return Value + /// + /// Returns `ReturnCode::Success` when the query was successfully created. When the query + /// creation fails, `ReturnCode::XcmQueryFailed` is returned. + #[deprecated( + note = "Unstable function. Behaviour can change without further notice. Use only for testing." + )] + fn xcm_query(timeout: &[u8], match_querier: &[u8], output: &mut [u8]) -> Result; + + /// Take an XCM response for the specified query. + /// + /// # Parameters + /// + /// - `query_id`: The [`xcm_builder::QueryHandler::QueryId`] + /// - `output`: A reference to the output data buffer to write the + /// [`xcm_builder::QueryResponseStatus`]. + #[deprecated( + note = "Unstable function. Behaviour can change without further notice. Use only for testing." + )] + fn xcm_take_response(query_id: &[u8], output: &mut [u8]) -> Result; } diff --git a/substrate/frame/contracts/uapi/src/host/riscv32.rs b/substrate/frame/contracts/uapi/src/host/riscv32.rs index dbd5abc42409..d2191aa136b3 100644 --- a/substrate/frame/contracts/uapi/src/host/riscv32.rs +++ b/substrate/frame/contracts/uapi/src/host/riscv32.rs @@ -304,4 +304,12 @@ impl HostFn for HostFnImpl { fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { todo!() } + + fn xcm_query(timeout: &[u8], match_querier: &[u8], output: &mut &mut [u8]) -> Result { + todo!() + } + + fn xcm_take_response(query_id: &[u8], output: &mut [u8]) { + todo!() + } } diff --git a/substrate/frame/contracts/uapi/src/host/wasm32.rs b/substrate/frame/contracts/uapi/src/host/wasm32.rs index 9651aa73d6f9..1a36a1092c06 100644 --- a/substrate/frame/contracts/uapi/src/host/wasm32.rs +++ b/substrate/frame/contracts/uapi/src/host/wasm32.rs @@ -168,6 +168,14 @@ mod sys { msg_len: u32, output_ptr: *mut u8, ) -> ReturnCode; + + pub fn xcm_query( + timeout_ptr: *const u8, + match_querier: *const u8, + output_ptr: *mut u8, + ) -> ReturnCode; + + pub fn xcm_take_response(query_id_ptr: *const u8, output_ptr: *mut u8) -> ReturnCode; } pub mod v1 { @@ -830,4 +838,16 @@ impl HostFn for HostFnImpl { }; ret_code.into() } + + fn xcm_query(timeout: &[u8], match_querier: &[u8], output: &mut [u8]) -> Result { + let ret_code = unsafe { + sys::xcm_query(timeout.as_ptr(), match_querier.as_ptr(), output.as_mut_ptr()) + }; + ret_code.into() + } + + fn xcm_take_response(query_id: &[u8], output: &mut [u8]) -> Result { + let ret_code = unsafe { sys::xcm_take_response(query_id.as_ptr(), output.as_mut_ptr()) }; + ret_code.into() + } } diff --git a/substrate/frame/contracts/uapi/src/lib.rs b/substrate/frame/contracts/uapi/src/lib.rs index 83877c6efd40..c1d69524d888 100644 --- a/substrate/frame/contracts/uapi/src/lib.rs +++ b/substrate/frame/contracts/uapi/src/lib.rs @@ -103,6 +103,8 @@ define_error_codes! { XcmExecutionFailed = 13, /// The `xcm_send` call failed. XcmSendFailed = 14, + /// The `xcm_query` call failed. + XcmQueryFailed = 15, } /// The raw return code returned by the host side.