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

Contracts: XCM queries & response integration #2355

Closed
wants to merge 5 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix up previous code
pgherveou committed Feb 27, 2024
commit d9569141c8a7cd6ccd5f1f3b70581f8fcf14f2e3
37 changes: 37 additions & 0 deletions substrate/frame/contracts/fixtures/contracts/xcm_query.rs
Original file line number Diff line number Diff line change
@@ -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);
}
38 changes: 38 additions & 0 deletions substrate/frame/contracts/fixtures/contracts/xcm_take_response.rs
Original file line number Diff line number Diff line change
@@ -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);
}
107 changes: 105 additions & 2 deletions substrate/frame/contracts/mock-network/src/tests.rs
Original file line number Diff line number Diff line change
@@ -16,25 +16,29 @@
// 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::{
assert_err,
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<parachain::Runtime>;
type QueryId = <pallet_xcm::Pallet<parachain::Runtime> 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<parachain::Runtime> = 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 = <ParachainPalletXcm as QueryHandler>::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::<BlockNumberFor<parachain::Runtime>>::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));
})
}
84 changes: 82 additions & 2 deletions substrate/frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
@@ -2075,7 +2075,7 @@ pub mod env {
) -> Result<ReturnErrorCode, TrapReason> {
let str_len = str_len.min(DebugBufferVec::<E::T>::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<ReturnErrorCode, TrapReason> {
use frame_system::pallet_prelude::BlockNumberFor;
use xcm::VersionedLocation;
use xcm_builder::{QueryController, QueryControllerWeightInfo};

let timeout: BlockNumberFor<E::T> = ctx.read_sandbox_memory_as(memory, timeout_ptr)?;
let match_querier: VersionedLocation =
ctx.read_sandbox_memory_as(memory, match_querier_ptr)?;

let weight = <<E::T as Config>::Xcm as QueryController<_, _>>::WeightInfo::query();
ctx.charge_gas(RuntimeCosts::CallRuntime(weight))?;
let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into();

match <<E::T as Config>::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<ReturnErrorCode, TrapReason> {
use xcm_builder::{QueryController, QueryControllerWeightInfo, QueryHandler};

let query_id: <<E::T as Config>::Xcm as QueryHandler>::QueryId =
ctx.read_sandbox_memory_as(memory, query_id_ptr)?;

let weight = <<E::T as Config>::Xcm as QueryController<_, _>>::WeightInfo::take_response();
ctx.charge_gas(RuntimeCosts::CallRuntime(weight))?;

let response = <<E::T as Config>::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]
32 changes: 31 additions & 1 deletion substrate/frame/contracts/uapi/src/host.rs
Original file line number Diff line number Diff line change
@@ -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<T>`.
/// - `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;
}
8 changes: 8 additions & 0 deletions substrate/frame/contracts/uapi/src/host/riscv32.rs
Original file line number Diff line number Diff line change
@@ -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!()
}
}
Loading