lib.rs 11 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

#![cfg_attr(not(feature = "std"), no_std)]

Gavin Wood's avatar
Gavin Wood committed
19
20
21
22
23
use sp_std::{prelude::*, marker::PhantomData};
use frame_support::{
	ensure, weights::GetDispatchInfo,
	dispatch::{Weight, Dispatchable}
};
24
use xcm::v0::{
Gavin Wood's avatar
Gavin Wood committed
25
26
	ExecuteXcm, SendXcm, Error as XcmError, Outcome,
	MultiLocation, MultiAsset, Xcm, Order, Response,
27
28
29
};

pub mod traits;
Gavin Wood's avatar
Gavin Wood committed
30
31
32
33
use traits::{
	TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation, WeightBounds, WeightTrader, ShouldExecute,
	OnResponse
};
34

Gavin Wood's avatar
Gavin Wood committed
35
mod assets;
36
pub use assets::{Assets, AssetId};
Gavin Wood's avatar
Gavin Wood committed
37
mod config;
38
39
40
41
pub use config::Config;

pub struct XcmExecutor<Config>(PhantomData<Config>);

Gavin Wood's avatar
Gavin Wood committed
42
impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
Gavin Wood's avatar
Gavin Wood committed
43
	type Call = Config::Call;
Gavin Wood's avatar
Gavin Wood committed
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
	fn execute_xcm(origin: MultiLocation, message: Xcm<Config::Call>, weight_limit: Weight) -> Outcome {
		// TODO: #2841 #HARDENXCM We should identify recursive bombs here and bail.
		let mut message = Xcm::<Config::Call>::from(message);
		let shallow_weight = match Config::Weigher::shallow(&mut message) {
			Ok(x) => x,
			Err(()) => return Outcome::Error(XcmError::WeightNotComputable),
		};
		let deep_weight = match Config::Weigher::deep(&mut message) {
			Ok(x) => x,
			Err(()) => return Outcome::Error(XcmError::WeightNotComputable),
		};
		let maximum_weight = match shallow_weight.checked_add(deep_weight) {
			Some(x) => x,
			None => return Outcome::Error(XcmError::WeightLimitReached),
		};
		if maximum_weight > weight_limit {
			return Outcome::Error(XcmError::WeightLimitReached);
		}
		let mut trader = Config::Trader::new();
		match Self::do_execute_xcm(origin, true, message, &mut 0, Some(shallow_weight), &mut trader) {
			Ok(surplus) => Outcome::Complete(maximum_weight.saturating_sub(surplus)),
			// TODO: #2841 #REALWEIGHT We can do better than returning `maximum_weight` here, and we should otherwise
			//  we'll needlessly be disregarding block execution time.
			Err(e) => Outcome::Incomplete(maximum_weight, e),
		}
	}
}

impl<Config: config::Config> XcmExecutor<Config> {
	fn reanchored(mut assets: Assets, dest: &MultiLocation) -> Vec<MultiAsset> {
		let inv_dest = Config::LocationInverter::invert_location(&dest);
		assets.prepend_location(&inv_dest);
		assets.into_assets_iter().collect::<Vec<_>>()
	}

	/// Execute the XCM and return any unexpected and unknowable surplus weight.
	fn do_execute_xcm(
		origin: MultiLocation,
		top_level: bool,
		mut message: Xcm<Config::Call>,
		weight_credit: &mut Weight,
		maybe_shallow_weight: Option<Weight>,
		trader: &mut Config::Trader,
	) -> Result<Weight, XcmError> {
		// This is the weight of everything that cannot be paid for. This basically means all computation
		// except any XCM which is behind an Order::BuyExecution.
		let shallow_weight = maybe_shallow_weight
			.or_else(|| Config::Weigher::shallow(&mut message).ok())
			.ok_or(XcmError::WeightNotComputable)?;

		Config::Barrier::should_execute(&origin, top_level, &message, shallow_weight, weight_credit)
			.map_err(|()| XcmError::Barrier)?;

		// The surplus weight, defined as the amount by which `shallow_weight` plus all nested
		// `shallow_weight` values (ensuring no double-counting) is an overestimate of the actual weight
		// consumed.
		let mut total_surplus: Weight = 0;

		let maybe_holding_effects = match (origin.clone(), message) {
103
104
105
106
			(origin, Xcm::WithdrawAsset { assets, effects }) => {
				// Take `assets` from the origin account (on-chain) and place in holding.
				let mut holding = Assets::default();
				for asset in assets {
Gavin Wood's avatar
Gavin Wood committed
107
					ensure!(!asset.is_wildcard(), XcmError::Wildcard);
108
					let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?;
Gavin Wood's avatar
Gavin Wood committed
109
					holding.saturating_subsume_all(withdrawn);
110
				}
Gavin Wood's avatar
Gavin Wood committed
111
				Some((holding, effects))
112
113
114
			}
			(origin, Xcm::ReserveAssetDeposit { assets, effects }) => {
				// check whether we trust origin to be our reserve location for this asset.
Gavin Wood's avatar
Gavin Wood committed
115
116
				for asset in assets.iter() {
					ensure!(!asset.is_wildcard(), XcmError::Wildcard);
117
118
					// We only trust the origin to send us assets that they identify as their
					// sovereign assets.
Gavin Wood's avatar
Gavin Wood committed
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
					ensure!(Config::IsReserve::filter_asset_location(asset, &origin), XcmError::UntrustedReserveLocation);
				}
				Some((Assets::from(assets), effects))
			}
			(origin, Xcm::TransferAsset { assets, dest }) => {
				// Take `assets` from the origin account (on-chain) and place into dest account.
				for asset in assets {
					ensure!(!asset.is_wildcard(), XcmError::Wildcard);
					Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?;
				}
				None
			}
			(origin, Xcm::TransferReserveAsset { mut assets, dest, effects }) => {
				// Take `assets` from the origin account (on-chain) and place into dest account.
				let inv_dest = Config::LocationInverter::invert_location(&dest);
				for asset in assets.iter_mut() {
					ensure!(!asset.is_wildcard(), XcmError::Wildcard);
					Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?;
					asset.reanchor(&inv_dest)?;
138
				}
Gavin Wood's avatar
Gavin Wood committed
139
140
				Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })?;
				None
141
142
143
			}
			(origin, Xcm::TeleportAsset { assets, effects }) => {
				// check whether we trust origin to teleport this asset to us via config trait.
Gavin Wood's avatar
Gavin Wood committed
144
145
				for asset in assets.iter() {
					ensure!(!asset.is_wildcard(), XcmError::Wildcard);
146
147
					// We only trust the origin to send us assets that they identify as their
					// sovereign assets.
Gavin Wood's avatar
Gavin Wood committed
148
					ensure!(Config::IsTeleporter::filter_asset_location(asset, &origin), XcmError::UntrustedTeleportLocation);
149
				}
Gavin Wood's avatar
Gavin Wood committed
150
				Some((Assets::from(assets), effects))
151
			}
Gavin Wood's avatar
Gavin Wood committed
152
			(origin, Xcm::Transact { origin_type, require_weight_at_most,  mut call }) => {
153
154
				// We assume that the Relay-chain is allowed to use transact on this parachain.

Gavin Wood's avatar
Gavin Wood committed
155
156
				// TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain
				let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?;
157
158
				let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type)
					.map_err(|_| XcmError::BadOrigin)?;
Gavin Wood's avatar
Gavin Wood committed
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
				let weight = message_call.get_dispatch_info().weight;
				ensure!(weight <= require_weight_at_most, XcmError::TooMuchWeightRequired);
				let actual_weight = match message_call.dispatch(dispatch_origin) {
					Ok(post_info) => post_info.actual_weight,
					Err(error_and_info) => {
						// Not much to do with the result as it is. It's up to the parachain to ensure that the
						// message makes sense.
						error_and_info.post_info.actual_weight
					}
				}.unwrap_or(weight);
				let surplus = weight.saturating_sub(actual_weight);
				// Credit any surplus weight that we bought. This should be safe since it's work we
				// didn't realise that we didn't have to do.
				// It works because we assume that the `Config::Weigher` will always count the `call`'s
				// `get_dispatch_info` weight into its `shallow` estimate.
				*weight_credit = weight_credit.saturating_add(surplus);
				// Do the same for the total surplus, which is reported to the caller and eventually makes its way
				// back up the stack to be subtracted from the deep-weight.
				total_surplus = total_surplus.saturating_add(surplus);
				// Return the overestimated amount so we can adjust our expectations on how much this entire
				// execution has taken.
				None
			}
			(origin, Xcm::QueryResponse { query_id, response }) => {
				Config::ResponseHandler::on_response(origin, query_id, response);
				None
185
			}
186
187
188
189
190
191
192
193
			(origin, Xcm::RelayedFrom { who, message }) => {
				ensure!(who.is_interior(), XcmError::EscalationOfPrivilege);
				let mut origin = origin;
				origin.append_with(who).map_err(|_| XcmError::MultiLocationFull)?;
				let surplus = Self::do_execute_xcm(origin, top_level, *message, weight_credit, None, trader)?;
				total_surplus = total_surplus.saturating_add(surplus);
				None
			}
194
195
196
			_ => Err(XcmError::UnhandledXcmMessage)?,	// Unhandled XCM message.
		};

Gavin Wood's avatar
Gavin Wood committed
197
198
199
200
		if let Some((mut holding, effects)) = maybe_holding_effects {
			for effect in effects.into_iter() {
				total_surplus += Self::execute_effects(&origin, &mut holding, effect, trader)?;
			}
201
202
		}

Gavin Wood's avatar
Gavin Wood committed
203
		Ok(total_surplus)
204
205
	}

Gavin Wood's avatar
Gavin Wood committed
206
207
208
209
210
211
212
	fn execute_effects(
		origin: &MultiLocation,
		holding: &mut Assets,
		effect: Order<Config::Call>,
		trader: &mut Config::Trader,
	) -> Result<Weight, XcmError> {
		let mut total_surplus = 0;
213
214
215
216
217
218
219
220
221
222
223
224
225
		match effect {
			Order::DepositAsset { assets, dest } => {
				let deposited = holding.saturating_take(assets);
				for asset in deposited.into_assets_iter() {
					Config::AssetTransactor::deposit_asset(&asset, &dest)?;
				}
			},
			Order::DepositReserveAsset { assets, dest, effects } => {
				let deposited = holding.saturating_take(assets);
				for asset in deposited.assets_iter() {
					Config::AssetTransactor::deposit_asset(&asset, &dest)?;
				}
				let assets = Self::reanchored(deposited, &dest);
Gavin Wood's avatar
Gavin Wood committed
226
				Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })?;
227
228
229
			},
			Order::InitiateReserveWithdraw { assets, reserve, effects} => {
				let assets = Self::reanchored(holding.saturating_take(assets), &reserve);
Gavin Wood's avatar
Gavin Wood committed
230
				Config::XcmSender::send_xcm(reserve, Xcm::WithdrawAsset { assets, effects })?;
231
232
233
			}
			Order::InitiateTeleport { assets, dest, effects} => {
				let assets = Self::reanchored(holding.saturating_take(assets), &dest);
Gavin Wood's avatar
Gavin Wood committed
234
				Config::XcmSender::send_xcm(dest, Xcm::TeleportAsset { assets, effects })?;
235
236
237
			}
			Order::QueryHolding { query_id, dest, assets } => {
				let assets = Self::reanchored(holding.min(assets.iter()), &dest);
Gavin Wood's avatar
Gavin Wood committed
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
				Config::XcmSender::send_xcm(dest, Xcm::QueryResponse { query_id, response: Response::Assets(assets) })?;
			}
			Order::BuyExecution { fees, weight, debt, halt_on_error, xcm } => {
				// pay for `weight` using up to `fees` of the holding account.
				let purchasing_weight = Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?);
				let max_fee = holding.try_take(fees).map_err(|()| XcmError::NotHoldingFees)?;
				let unspent = trader.buy_weight(purchasing_weight, max_fee)?;
				holding.saturating_subsume_all(unspent);

				let mut remaining_weight = weight;
				for message in xcm.into_iter() {
					match Self::do_execute_xcm(origin.clone(), false, message, &mut remaining_weight, None, trader) {
						Err(e) if halt_on_error => return Err(e),
						Err(_) => {}
						Ok(surplus) => { total_surplus += surplus }
					}
				}
				holding.saturating_subsume(trader.refund_weight(remaining_weight));
256
			}
Gavin Wood's avatar
Gavin Wood committed
257
			_ => return Err(XcmError::UnhandledEffect)?,
258
		}
Gavin Wood's avatar
Gavin Wood committed
259
		Ok(total_surplus)
260
261
	}
}