diff --git a/polkadot/xcm/xcm-executor/src/traits/on_response.rs b/polkadot/xcm/xcm-executor/src/traits/on_response.rs index 952bd2d0040a..53c43573f1d1 100644 --- a/polkadot/xcm/xcm-executor/src/traits/on_response.rs +++ b/polkadot/xcm/xcm-executor/src/traits/on_response.rs @@ -98,7 +98,7 @@ impl VersionChangeNotifier for () { } /// The possible state of an XCM query response. -#[derive(Debug, PartialEq, Eq, Encode, Decode)] +#[derive(Debug, PartialEq, Eq, Encode, Decode, TypeInfo)] pub enum QueryResponseStatus { /// The response has arrived, and includes the inner Response and the block number it arrived /// at. 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..8ce664433ea9 --- /dev/null +++ b/substrate/frame/contracts/fixtures/contracts/xcm_query.rs @@ -0,0 +1,40 @@ +// 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; 4], 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..847dd1e4c572 --- /dev/null +++ b/substrate/frame/contracts/fixtures/contracts/xcm_take_response.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!(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/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index 8a17a3f2fa78..dfed77589d3c 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -22,12 +22,20 @@ pub mod relay_chain; #[cfg(test)] mod tests; -use crate::primitives::{AccountId, UNITS}; +use crate::primitives::{ + AccountId, + UNITS, +}; use sp_runtime::BuildStorage; use xcm::latest::prelude::*; +pub use xcm_executor; use xcm_executor::traits::ConvertLocation; pub use xcm_simulator::TestExt; -use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; +use xcm_simulator::{ + decl_test_network, + decl_test_parachain, + decl_test_relay_chain, +}; // Accounts pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); @@ -38,114 +46,132 @@ pub const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([2u8; 32]) pub const INITIAL_BALANCE: u128 = 1_000_000_000 * UNITS; decl_test_parachain! { - pub struct ParaA { - Runtime = parachain::Runtime, - XcmpMessageHandler = parachain::MsgQueue, - DmpMessageHandler = parachain::MsgQueue, - new_ext = para_ext(1), - } + pub struct ParaA { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(1), + } } decl_test_relay_chain! { - pub struct Relay { - Runtime = relay_chain::Runtime, - RuntimeCall = relay_chain::RuntimeCall, - RuntimeEvent = relay_chain::RuntimeEvent, - XcmConfig = relay_chain::XcmConfig, - MessageQueue = relay_chain::MessageQueue, - System = relay_chain::System, - new_ext = relay_ext(), - } + pub struct Relay { + Runtime = relay_chain::Runtime, + RuntimeCall = relay_chain::RuntimeCall, + RuntimeEvent = relay_chain::RuntimeEvent, + XcmConfig = relay_chain::XcmConfig, + MessageQueue = relay_chain::MessageQueue, + System = relay_chain::System, + new_ext = relay_ext(), + } } decl_test_network! { - pub struct MockNet { - relay_chain = Relay, - parachains = vec![ - (1, ParaA), - ], - } + pub struct MockNet { + relay_chain = Relay, + parachains = vec![ + (1, ParaA), + ], + } } pub fn relay_sovereign_account_id() -> AccountId { - let location: Location = (Parent,).into(); - parachain::SovereignAccountOf::convert_location(&location).unwrap() + let location: Location = (Parent,).into(); + parachain::SovereignAccountOf::convert_location(&location).unwrap() } pub fn parachain_sovereign_account_id(para: u32) -> AccountId { - let location: Location = (Parachain(para),).into(); - relay_chain::SovereignAccountOf::convert_location(&location).unwrap() + let location: Location = (Parachain(para),).into(); + relay_chain::SovereignAccountOf::convert_location(&location).unwrap() } pub fn parachain_account_sovereign_account_id( - para: u32, - who: sp_runtime::AccountId32, + para: u32, + who: sp_runtime::AccountId32, ) -> AccountId { - let location: Location = ( - Parachain(para), - AccountId32 { network: Some(relay_chain::RelayNetwork::get()), id: who.into() }, - ) - .into(); - relay_chain::SovereignAccountOf::convert_location(&location).unwrap() + let location: Location = ( + Parachain(para), + AccountId32 { + network: Some(relay_chain::RelayNetwork::get()), + id: who.into(), + }, + ) + .into(); + relay_chain::SovereignAccountOf::convert_location(&location).unwrap() } pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { - use parachain::{MsgQueue, Runtime, System}; - - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![ - (ALICE, INITIAL_BALANCE), - (relay_sovereign_account_id(), INITIAL_BALANCE), - (BOB, INITIAL_BALANCE), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - pallet_assets::GenesisConfig:: { - assets: vec![ - (0u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token - ], - metadata: Default::default(), - accounts: vec![ - (0u128, ALICE, INITIAL_BALANCE), - (0u128, relay_sovereign_account_id(), INITIAL_BALANCE), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| { - sp_tracing::try_init_simple(); - System::set_block_number(1); - MsgQueue::set_para_id(para_id.into()); - }); - ext + use parachain::{ + MsgQueue, + Runtime, + System, + }; + + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (relay_sovereign_account_id(), INITIAL_BALANCE), + (BOB, INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_assets::GenesisConfig:: { + assets: vec![ + (0u128, ADMIN, false, 1u128), /* Create derivative asset for relay's + * native token */ + ], + metadata: Default::default(), + accounts: vec![ + (0u128, ALICE, INITIAL_BALANCE), + (0u128, relay_sovereign_account_id(), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + sp_tracing::try_init_simple(); + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + }); + ext } pub fn relay_ext() -> sp_io::TestExternalities { - use relay_chain::{Runtime, System}; - - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![ - (ALICE, INITIAL_BALANCE), - (parachain_sovereign_account_id(1), INITIAL_BALANCE), - (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| { - System::set_block_number(1); - }); - ext + use relay_chain::{ + Runtime, + System, + }; + + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (parachain_sovereign_account_id(1), INITIAL_BALANCE), + ( + parachain_account_sovereign_account_id(1, ALICE), + INITIAL_BALANCE, + ), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext } pub type ParachainPalletXcm = pallet_xcm::Pallet; diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index d4ad47581d16..7f044b1d8455 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -50,7 +50,7 @@ pub type SovereignAccountOf = (AccountId32Aliases, ParentIsPreset); parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u32 = 250; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] @@ -328,7 +328,7 @@ impl pallet_xcm::Config for Runtime { type AdminOrigin = EnsureRoot; } -type Block = frame_system::mocking::MockBlock; +type Block = frame_system::mocking::MockBlockU32; impl pallet_timestamp::Config for Runtime { type Moment = u64; diff --git a/substrate/frame/contracts/mock-network/src/tests.rs b/substrate/frame/contracts/mock-network/src/tests.rs index d22221fe8ee0..4de25b08d9b9 100644 --- a/substrate/frame/contracts/mock-network/src/tests.rs +++ b/substrate/frame/contracts/mock-network/src/tests.rs @@ -16,263 +16,454 @@ // limitations under the License. use crate::{ - parachain::{self, Runtime}, - parachain_account_sovereign_account_id, - primitives::{AccountId, CENTS}, - relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, + parachain::{ + self, + Runtime, + RuntimeOrigin, + }, + parachain_account_sovereign_account_id, + primitives::{ + AccountId, + CENTS, + }, + relay_chain, + MockNet, + ParaA, + ParachainBalances, + ParachainPalletXcm, + Relay, + ALICE, + BOB, + INITIAL_BALANCE, +}; +use codec::{ + Decode, + Encode, }; -use codec::{Decode, Encode}; use frame_support::{ - assert_err, - pallet_prelude::Weight, - traits::{fungibles::Mutate, Currency}, + 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_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::{ + 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 $(,)? ) => {{ - assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); - }}; + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!( + u32::from_le_bytes($x.data[..].try_into().unwrap()), + $y as u32 + ); + }}; } /// Instantiate the tests contract, and fund it with some balance and assets. fn instantiate_test_contract(name: &str) -> AccountId { - let (wasm, _) = compile_module::(name).unwrap(); - - // Instantiate contract. - let contract_addr = ParaA::execute_with(|| { - ParachainContracts::bare_instantiate( - ALICE, - 0, - Weight::MAX, - None, - Code::Upload(wasm), - vec![], - vec![], - DebugInfo::UnsafeDebug, - CollectEvents::Skip, - ) - .result - .unwrap() - .account_id - }); - - // Funds contract account with some balance and assets. - ParaA::execute_with(|| { - parachain::Balances::make_free_balance_be(&contract_addr, INITIAL_BALANCE); - parachain::Assets::mint_into(0u32.into(), &contract_addr, INITIAL_BALANCE).unwrap(); - }); - Relay::execute_with(|| { - let sovereign_account = parachain_account_sovereign_account_id(1u32, contract_addr.clone()); - relay_chain::Balances::make_free_balance_be(&sovereign_account, INITIAL_BALANCE); - }); - - contract_addr + let (wasm, _) = compile_module::(name).unwrap(); + + // Instantiate contract. + let contract_addr = ParaA::execute_with(|| { + ParachainContracts::bare_instantiate( + ALICE, + 0, + Weight::MAX, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id + }); + + // Funds contract account with some balance and assets. + ParaA::execute_with(|| { + parachain::Balances::make_free_balance_be(&contract_addr, INITIAL_BALANCE); + parachain::Assets::mint_into(0u32.into(), &contract_addr, INITIAL_BALANCE) + .unwrap(); + }); + Relay::execute_with(|| { + let sovereign_account = + parachain_account_sovereign_account_id(1u32, contract_addr.clone()); + relay_chain::Balances::make_free_balance_be(&sovereign_account, INITIAL_BALANCE); + }); + + contract_addr } #[test] fn test_xcm_execute() { - MockNet::reset(); - - let contract_addr = instantiate_test_contract("xcm_execute"); - - // Execute XCM instructions through the contract. - ParaA::execute_with(|| { - let amount: u128 = 10 * CENTS; - - // The XCM used to transfer funds to Bob. - let message: Xcm<()> = Xcm(vec![ - WithdrawAsset(vec![(Here, amount).into()].into()), - DepositAsset { - assets: All.into(), - beneficiary: AccountId32 { network: None, id: BOB.clone().into() }.into(), - }, - ]); - - let result = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - VersionedXcm::V4(message).encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); - - assert_eq!(result.gas_consumed, result.gas_required); - assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); - - // Check if the funds are subtracted from the account of Alice and added to the account of - // Bob. - let initial = INITIAL_BALANCE; - assert_eq!(ParachainBalances::free_balance(BOB), initial + amount); - assert_eq!(ParachainBalances::free_balance(&contract_addr), initial - amount); - }); + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + let amount: u128 = 10 * CENTS; + + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm(vec![ + WithdrawAsset(vec![(Here, amount).into()].into()), + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { + network: None, + id: BOB.clone().into(), + } + .into(), + }, + ]); + + let result = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + VersionedXcm::V4(message).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); + + // Check if the funds are subtracted from the account of Alice and added to the + // account of Bob. + let initial = INITIAL_BALANCE; + assert_eq!(ParachainBalances::free_balance(BOB), initial + amount); + assert_eq!( + ParachainBalances::free_balance(&contract_addr), + initial - amount + ); + }); } #[test] fn test_xcm_execute_incomplete() { - MockNet::reset(); - - let contract_addr = instantiate_test_contract("xcm_execute"); - let amount = 10 * CENTS; - - // Execute XCM instructions through the contract. - ParaA::execute_with(|| { - // The XCM used to transfer funds to Bob. - let message: Xcm<()> = Xcm(vec![ - WithdrawAsset(vec![(Here, amount).into()].into()), - // This will fail as the contract does not have enough balance to complete both - // withdrawals. - WithdrawAsset(vec![(Here, INITIAL_BALANCE).into()].into()), - DepositAsset { - assets: All.into(), - beneficiary: AccountId32 { network: None, id: BOB.clone().into() }.into(), - }, - ]); - - let result = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - VersionedXcm::V4(message).encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); - - assert_eq!(result.gas_consumed, result.gas_required); - assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); - - assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); - assert_eq!(ParachainBalances::free_balance(&contract_addr), INITIAL_BALANCE - amount); - }); + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + let amount = 10 * CENTS; + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm(vec![ + WithdrawAsset(vec![(Here, amount).into()].into()), + // This will fail as the contract does not have enough balance to complete + // both withdrawals. + WithdrawAsset(vec![(Here, INITIAL_BALANCE).into()].into()), + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { + network: None, + id: BOB.clone().into(), + } + .into(), + }, + ]); + + let result = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + VersionedXcm::V4(message).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + assert_eq!( + ParachainBalances::free_balance(&contract_addr), + INITIAL_BALANCE - amount + ); + }); } #[test] fn test_xcm_execute_filtered_call() { - MockNet::reset(); - - let contract_addr = instantiate_test_contract("xcm_execute"); - - ParaA::execute_with(|| { - // `remark` should be rejected, as it is not allowed by our CallFilter. - let call = parachain::RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); - let message: Xcm = Xcm(vec![Transact { - origin_kind: OriginKind::Native, - require_weight_at_most: Weight::MAX, - call: call.encode().into(), - }]); - - let result = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - VersionedXcm::V4(message).encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); - - assert_err!(result.result, frame_system::Error::::CallFiltered); - }); + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + ParaA::execute_with(|| { + // `remark` should be rejected, as it is not allowed by our CallFilter. + let call = + parachain::RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + let message: Xcm = Xcm(vec![Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::MAX, + call: call.encode().into(), + }]); + + let result = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + VersionedXcm::V4(message).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + assert_err!( + result.result, + frame_system::Error::::CallFiltered + ); + }); } #[test] fn test_xcm_execute_reentrant_call() { - MockNet::reset(); - - let contract_addr = instantiate_test_contract("xcm_execute"); - - ParaA::execute_with(|| { - let transact_call = parachain::RuntimeCall::Contracts(pallet_contracts::Call::call { - dest: contract_addr.clone(), - gas_limit: 1_000_000.into(), - storage_deposit_limit: None, - data: vec![], - value: 0u128, - }); - - // The XCM used to transfer funds to Bob. - let message: Xcm = Xcm(vec![ - Transact { - origin_kind: OriginKind::Native, - require_weight_at_most: 1_000_000_000.into(), - call: transact_call.encode().into(), - }, - ExpectTransactStatus(MaybeErrorCode::Success), - ]); - - let result = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - VersionedXcm::V4(message).encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); - - assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); - - // Funds should not change hands as the XCM transact failed. - assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); - }); + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + ParaA::execute_with(|| { + let transact_call = + parachain::RuntimeCall::Contracts(pallet_contracts::Call::call { + dest: contract_addr.clone(), + gas_limit: 1_000_000.into(), + storage_deposit_limit: None, + data: vec![], + value: 0u128, + }); + + // The XCM used to transfer funds to Bob. + let message: Xcm = Xcm(vec![ + Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: 1_000_000_000.into(), + call: transact_call.encode().into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ]); + + let result = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + VersionedXcm::V4(message).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + + // Funds should not change hands as the XCM transact failed. + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + }); } #[test] fn test_xcm_send() { - MockNet::reset(); - let contract_addr = instantiate_test_contract("xcm_send"); - let fee = parachain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` - - // Send XCM instructions through the contract, to lock some funds on the relay chain. - ParaA::execute_with(|| { - let dest = Location::from(Parent); - let dest = VersionedLocation::V4(dest); - - let message: Xcm<()> = Xcm(vec![ - WithdrawAsset((Here, fee).into()), - BuyExecution { fees: (Here, fee).into(), weight_limit: WeightLimit::Unlimited }, - LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(1)).into() }, - ]); - let message = VersionedXcm::V4(message); - let exec = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - (dest, message).encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); - - let mut data = &exec.result.unwrap().data[..]; - XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id"); - }); - - Relay::execute_with(|| { - // Check if the funds are locked on the relay chain. - assert_eq!( - relay_chain::Balances::locks(¶chain_account_sovereign_account_id(1, contract_addr)), - vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * CENTS, reasons: Reasons::All }] - ); - }); + MockNet::reset(); + let contract_addr = instantiate_test_contract("xcm_send"); + let fee = parachain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` + + // Send XCM instructions through the contract, to lock some funds on the relay chain. + ParaA::execute_with(|| { + let dest = Location::from(Parent); + let dest = VersionedLocation::V4(dest); + + let message: Xcm<()> = Xcm(vec![ + WithdrawAsset((Here, fee).into()), + BuyExecution { + fees: (Here, fee).into(), + weight_limit: WeightLimit::Unlimited, + }, + LockAsset { + asset: (Here, 5 * CENTS).into(), + unlocker: (Parachain(1)).into(), + }, + ]); + let message = VersionedXcm::V4(message); + let exec = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + (dest, message).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + let mut data = &exec.result.unwrap().data[..]; + XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id"); + }); + + Relay::execute_with(|| { + // Check if the funds are locked on the relay chain. + assert_eq!( + relay_chain::Balances::locks(¶chain_account_sovereign_account_id( + 1, + contract_addr + )), + vec![BalanceLock { + id: *b"py/xcmlk", + amount: 5 * CENTS, + reasons: Reasons::All + }] + ); + }); +} + +#[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 7d43d30ba580..9e6424f1c8b2 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -2074,7 +2074,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); @@ -2103,7 +2103,7 @@ pub mod env { } /// Execute an XCM program locally, using the contract's address as the origin. - /// See [`pallet_contracts_uapi::HostFn::execute_xcm`]. + /// See [`pallet_contracts_uapi::HostFn::xcm_execute`]. #[unstable] fn xcm_execute( ctx: _, @@ -2142,7 +2142,7 @@ pub mod env { } /// Send an XCM program from the contract to the specified destination. - /// See [`pallet_contracts_uapi::HostFn::send_xcm`]. + /// See [`pallet_contracts_uapi::HostFn::xcm_send`]. #[unstable] fn xcm_send( ctx: _, @@ -2170,7 +2170,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()); }; @@ -2179,6 +2179,83 @@ pub mod env { } } + /// Create a new query, using the contract's address as the responder. + /// See [`pallet_contracts_uapi::HostFn::xcm_query`]. + #[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(); + + // TODO: + // - Take deposit for query. + // - Keep a map of account_id to query_id. + + 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}; + + // TODO: + // - Ensure query_id belong to contract + // - Release deposit for query. + + 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/riscv32.rs b/substrate/frame/contracts/uapi/src/host/riscv32.rs index 561ab28747df..45ad44279e36 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 bc697238061a..1287d05835bb 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 { @@ -831,4 +839,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.