1#![cfg_attr(not(feature = "std"), no_std)]
20
21use astar_primitives::xcm::{
22 resolve_transfer_type, split_location_into_chain_part_and_beneficiary, ASSET_HUB_PARA_ID,
23 XCM_SIZE_LIMIT,
24};
25use fp_evm::PrecompileHandle;
26use frame_support::{
27 dispatch::{GetDispatchInfo, PostDispatchInfo},
28 pallet_prelude::Weight,
29 traits::{ConstU32, Get},
30};
31use sp_runtime::traits::{Dispatchable, MaybeEquivalence};
32
33use pallet_evm::{AddressMapping, GasWeightMapping};
34use sp_core::{H160, H256, U256};
35
36use sp_std::marker::PhantomData;
37use sp_std::prelude::*;
38
39use xcm::{latest::prelude::*, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm};
40use xcm_executor::traits::TransferType;
41
42use pallet_evm_precompile_assets_erc20::AddressToAssetId;
43use pallet_xcm::WeightInfo as PalletXcmWeightInfo;
44use precompile_utils::prelude::*;
45#[cfg(test)]
46mod mock;
47#[cfg(test)]
48mod tests;
49
50const NATIVE_ADDRESS: H160 = H160::zero();
52
53type GetXcmSizeLimit = ConstU32<XCM_SIZE_LIMIT>;
55
56pub const MAX_ASSETS_FOR_TRANSFER: u32 = 2;
58
59pub type GetMaxAssets = ConstU32<MAX_ASSETS_FOR_TRANSFER>;
61
62const DEFAULT_PROOF_SIZE: u64 = 1024 * 256;
64
65pub const REMOTE_CALL_SIZE_LIMIT: u32 = 64 * 1024;
67
68pub type GetRemoteCallSizeLimit = ConstU32<REMOTE_CALL_SIZE_LIMIT>;
70
71const SEND_XCM_UNSUPPORTED: &str =
73 "send_xcm is not supported: sending an arbitrary XCM requires Root. \
74 Use remote_transact(uint256,bool,address,uint256,bytes,uint64) for a sibling Transact.";
75
76pub struct XcmPrecompile<Runtime, C>(PhantomData<(Runtime, C)>);
78
79#[precompile_utils::precompile]
80#[precompile::test_concrete_types(mock::Runtime, mock::AssetIdConverter<mock::AssetId>)]
81impl<Runtime, C> XcmPrecompile<Runtime, C>
82where
83 Runtime: pallet_evm::Config
84 + pallet_xcm::Config
85 + pallet_assets::Config
86 + AddressToAssetId<<Runtime as pallet_assets::Config>::AssetId>,
87 <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
88 From<Option<Runtime::AccountId>>,
89 <Runtime as frame_system::Config>::RuntimeCall: From<pallet_xcm::Call<Runtime>>
90 + Dispatchable<PostInfo = PostDispatchInfo>
91 + GetDispatchInfo,
92 C: MaybeEquivalence<Location, <Runtime as pallet_assets::Config>::AssetId>,
93 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
94 Runtime::AccountId: Into<[u8; 32]>,
95{
96 #[precompile::public("assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)")]
104 fn assets_withdraw_native_v1(
105 handle: &mut impl PrecompileHandle,
106 assets: BoundedVec<Address, GetMaxAssets>,
107 amounts: BoundedVec<U256, GetMaxAssets>,
108 recipient_account_id: H256,
109 is_relay: bool,
110 parachain_id: U256,
111 fee_index: U256,
112 ) -> EvmResult<bool> {
113 Self::assets_transfer_v1(
114 handle,
115 assets,
116 amounts,
117 Self::beneficiary_32(recipient_account_id),
118 is_relay,
119 parachain_id,
120 fee_index,
121 false,
122 )
123 }
124
125 #[precompile::public("assets_withdraw(address[],uint256[],address,bool,uint256,uint256)")]
128 fn assets_withdraw_evm_v1(
129 handle: &mut impl PrecompileHandle,
130 assets: BoundedVec<Address, GetMaxAssets>,
131 amounts: BoundedVec<U256, GetMaxAssets>,
132 recipient_account_id: Address,
133 is_relay: bool,
134 parachain_id: U256,
135 fee_index: U256,
136 ) -> EvmResult<bool> {
137 Self::assets_transfer_v1(
138 handle,
139 assets,
140 amounts,
141 Self::beneficiary_key_20(recipient_account_id),
142 is_relay,
143 parachain_id,
144 fee_index,
145 false,
146 )
147 }
148
149 #[precompile::public(
155 "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)"
156 )]
157 fn assets_reserve_transfer_native_v1(
158 handle: &mut impl PrecompileHandle,
159 assets: BoundedVec<Address, GetMaxAssets>,
160 amounts: BoundedVec<U256, GetMaxAssets>,
161 recipient_account_id: H256,
162 is_relay: bool,
163 parachain_id: U256,
164 fee_index: U256,
165 ) -> EvmResult<bool> {
166 Self::assets_transfer_v1(
167 handle,
168 assets,
169 amounts,
170 Self::beneficiary_32(recipient_account_id),
171 is_relay,
172 parachain_id,
173 fee_index,
174 true,
175 )
176 }
177
178 #[precompile::public(
180 "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)"
181 )]
182 fn assets_reserve_transfer_evm_v1(
183 handle: &mut impl PrecompileHandle,
184 assets: BoundedVec<Address, GetMaxAssets>,
185 amounts: BoundedVec<U256, GetMaxAssets>,
186 recipient_account_id: Address,
187 is_relay: bool,
188 parachain_id: U256,
189 fee_index: U256,
190 ) -> EvmResult<bool> {
191 Self::assets_transfer_v1(
192 handle,
193 assets,
194 amounts,
195 Self::beneficiary_key_20(recipient_account_id),
196 is_relay,
197 parachain_id,
198 fee_index,
199 true,
200 )
201 }
202
203 #[precompile::public("transfer(address,uint256,(uint8,bytes[]),(uint64,uint64))")]
206 fn transfer(
207 handle: &mut impl PrecompileHandle,
208 currency_address: Address,
209 amount_of_tokens: U256,
210 destination: Location,
211 weight: WeightV2,
212 ) -> EvmResult<bool> {
213 let currency_address: H160 = currency_address.into();
214 let asset_location = if currency_address == NATIVE_ADDRESS {
216 Location::here()
217 } else {
218 let asset_id = Runtime::address_to_asset_id(currency_address)
219 .ok_or(revert("Failed to resolve asset id from address"))?;
220 C::convert_back(&asset_id).ok_or(revert(
221 "Failed to resolve asset multilocation from local id",
222 ))?
223 };
224 let asset: Asset = (asset_location, Self::amount(amount_of_tokens)?).into();
225
226 Self::transfer_to_combined_destination(handle, asset.into(), 0, destination, weight)
227 }
228
229 #[precompile::public(
231 "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),(uint64,uint64))"
232 )]
233 fn transfer_multiasset(
234 handle: &mut impl PrecompileHandle,
235 asset_location: Location,
236 amount_of_tokens: U256,
237 destination: Location,
238 weight: WeightV2,
239 ) -> EvmResult<bool> {
240 let asset: Asset = (asset_location, Self::amount(amount_of_tokens)?).into();
241
242 Self::transfer_to_combined_destination(handle, asset.into(), 0, destination, weight)
243 }
244
245 #[precompile::public(
248 "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
249 )]
250 fn transfer_multi_currencies(
251 handle: &mut impl PrecompileHandle,
252 currencies: BoundedVec<Currency, GetMaxAssets>,
253 fee_item: u32,
254 destination: Location,
255 weight: WeightV2,
256 ) -> EvmResult<bool> {
257 let currencies: Vec<Currency> = currencies.into();
258 let unsorted = currencies
259 .into_iter()
260 .map(|currency| {
261 Ok((
262 Self::asset_location(currency.get_address().into(), false)
263 .ok_or(revert("can't convert into currency id"))?,
264 Self::amount(currency.get_amount())?,
265 )
266 .into())
267 })
268 .collect::<EvmResult<Vec<Asset>>>()?;
269
270 let assets: Assets = unsorted.clone().into();
271 let fee_item = Self::fee_index_after_sort(&unsorted, &assets, fee_item)?;
272
273 Self::transfer_to_combined_destination(handle, assets, fee_item, destination, weight)
274 }
275
276 #[precompile::public(
280 "transfer_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
281 )]
282 fn transfer_multi_assets(
283 handle: &mut impl PrecompileHandle,
284 assets: BoundedVec<EvmMultiAsset, GetMaxAssets>,
285 fee_item: u32,
286 destination: Location,
287 weight: WeightV2,
288 ) -> EvmResult<bool> {
289 let assets: Vec<EvmMultiAsset> = assets.into();
290 let assets = assets
291 .into_iter()
292 .map(|asset| Ok((asset.get_location(), Self::amount(asset.get_amount())?).into()))
293 .collect::<EvmResult<Vec<Asset>>>()?;
294
295 let assets = Assets::from_sorted_and_deduplicated(assets).map_err(|_| {
296 revert("In field Assets, Provided assets either not sorted nor deduplicated")
297 })?;
298
299 Self::transfer_to_combined_destination(handle, assets, fee_item, destination, weight)
300 }
301
302 #[precompile::public(
304 "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
305 )]
306 fn transfer_with_fee(
307 handle: &mut impl PrecompileHandle,
308 currency_address: Address,
309 amount_of_tokens: U256,
310 fee: U256,
311 destination: Location,
312 weight: WeightV2,
313 ) -> EvmResult<bool> {
314 Self::transfer(
315 handle,
316 currency_address,
317 Self::total_with_fee(amount_of_tokens, fee)?,
318 destination,
319 weight,
320 )
321 }
322
323 #[precompile::public(
325 "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
326 )]
327 fn transfer_multiasset_with_fee(
328 handle: &mut impl PrecompileHandle,
329 asset_location: Location,
330 amount_of_tokens: U256,
331 fee: U256,
332 destination: Location,
333 weight: WeightV2,
334 ) -> EvmResult<bool> {
335 Self::transfer_multiasset(
336 handle,
337 asset_location,
338 Self::total_with_fee(amount_of_tokens, fee)?,
339 destination,
340 weight,
341 )
342 }
343
344 #[precompile::public("remote_transact(uint256,bool,address,uint256,bytes,uint64)")]
354 fn remote_transact_v1(
355 handle: &mut impl PrecompileHandle,
356 para_id: U256,
357 is_relay: bool,
358 fee_asset_addr: Address,
359 fee_amount: U256,
360 remote_call: BoundedBytes<GetRemoteCallSizeLimit>,
361 transact_weight: u64,
362 ) -> EvmResult<bool> {
363 if is_relay {
364 return Err(revert(
365 "remote_transact to the relay chain is not supported, use a sibling parachain",
366 ));
367 }
368
369 let dest = Self::chain_part(false, para_id)?;
370 let remote_call: Vec<u8> = remote_call.into();
371 let remote_call_len = remote_call.len() as u64;
372
373 let fee_asset_addr: H160 = fee_asset_addr.into();
374 let fee_asset = if fee_asset_addr == NATIVE_ADDRESS {
376 Location::here()
377 } else {
378 let fee_asset_id = Runtime::address_to_asset_id(fee_asset_addr)
379 .ok_or(revert("Failed to resolve fee asset id from address"))?;
380 C::convert_back(&fee_asset_id).ok_or(revert(
381 "Failed to resolve fee asset multilocation from local id",
382 ))?
383 };
384 let fee: Asset = (fee_asset, Self::amount(fee_amount)?).into();
385
386 let context = <Runtime as pallet_xcm::Config>::UniversalLocation::get();
387 let fee = fee
388 .reanchored(&dest, &context)
389 .map_err(|_| revert("Failed to reanchor fee asset"))?;
390
391 let message = Xcm(vec![
392 WithdrawAsset(fee.clone().into()),
393 BuyExecution {
394 fees: fee,
395 weight_limit: WeightLimit::Unlimited,
396 },
397 Transact {
398 origin_kind: OriginKind::SovereignAccount,
399 fallback_max_weight: Some(Weight::from_parts(transact_weight, DEFAULT_PROOF_SIZE)),
400 call: remote_call.into(),
401 },
402 ]);
403
404 let network = context.global_consensus().map_err(|_| {
409 revert(
410 "UniversalLocation carries no global consensus: cannot derive the caller's origin",
411 )
412 })?;
413 let interior = Junction::AccountId32 {
414 network: Some(network),
415 id: Runtime::AddressMapping::into_account_id(handle.context().caller).into(),
416 };
417
418 log::trace!(target: "xcm-precompile:remote_transact", "dest: {:?}, interior: {:?}, message: {:?}", dest, interior, message);
419
420 let weight = <Runtime as pallet_xcm::Config>::WeightInfo::send()
421 .saturating_add(Weight::from_parts(0, remote_call_len));
422 RuntimeHelper::<Runtime>::record_external_cost(handle, weight, 0)?;
423 handle.record_cost(
424 <Runtime as pallet_evm::Config>::GasWeightMapping::weight_to_gas(weight),
425 )?;
426
427 pallet_xcm::Pallet::<Runtime>::send_xcm(interior, dest, message).map_err(|error| {
428 log::trace!(target: "xcm-precompile:remote_transact", "send_xcm failed: {:?}", error);
429 revert("Failed to send xcm")
430 })?;
431
432 Ok(true)
433 }
434
435 #[precompile::public("send_xcm((uint8,bytes[]),bytes)")]
442 fn send_xcm(
443 handle: &mut impl PrecompileHandle,
444 dest: Location,
445 xcm_call: BoundedBytes<GetXcmSizeLimit>,
446 ) -> EvmResult<bool> {
447 let _ = (handle, dest, xcm_call);
448 Err(revert(SEND_XCM_UNSUPPORTED))
449 }
450
451 fn assets_transfer_v1(
461 handle: &mut impl PrecompileHandle,
462 assets: BoundedVec<Address, GetMaxAssets>,
463 amounts: BoundedVec<U256, GetMaxAssets>,
464 beneficiary: Location,
465 is_relay: bool,
466 parachain_id: U256,
467 fee_index: U256,
468 native_address: bool,
469 ) -> EvmResult<bool> {
470 let addresses: Vec<Address> = assets.into();
471 let locations = addresses
472 .into_iter()
473 .filter_map(|address| Self::asset_location(address.into(), native_address))
474 .collect::<Vec<Location>>();
475
476 let amounts: Vec<U256> = amounts.into();
477 let amounts = amounts
478 .into_iter()
479 .map(Self::amount)
480 .collect::<EvmResult<Vec<u128>>>()?;
481
482 if locations.len() != amounts.len() || locations.is_empty() {
486 return Err(revert("Assets resolution failure."));
487 }
488
489 let assets = locations
490 .into_iter()
491 .zip(amounts)
492 .map(Into::into)
493 .collect::<Vec<Asset>>();
494
495 let fee_index: u32 = fee_index
496 .try_into()
497 .map_err(|_| revert("error converting fee_index, maybe value too large"))?;
498
499 Self::do_transfer(
500 handle,
501 assets.into(),
502 fee_index,
503 Self::chain_part(is_relay, parachain_id)?,
504 beneficiary,
505 WeightLimit::Unlimited,
506 )
507 }
508
509 fn transfer_to_combined_destination(
512 handle: &mut impl PrecompileHandle,
513 assets: Assets,
514 fee_index: u32,
515 destination: Location,
516 weight: WeightV2,
517 ) -> EvmResult<bool> {
518 let (dest, beneficiary) = split_location_into_chain_part_and_beneficiary(destination)
519 .ok_or(revert(
520 "error splitting destination into chain and beneficiary",
521 ))?;
522
523 if beneficiary == Location::here() {
525 return Err(revert(
526 "destination carries no beneficiary: append the recipient junction to it",
527 ));
528 }
529
530 Self::do_transfer(
531 handle,
532 assets,
533 fee_index,
534 dest,
535 beneficiary,
536 Self::weight_limit(&weight)?,
537 )
538 }
539
540 fn do_transfer(
542 handle: &mut impl PrecompileHandle,
543 assets: Assets,
544 fee_index: u32,
545 dest: Location,
546 beneficiary: Location,
547 weight_limit: WeightLimit,
548 ) -> EvmResult<bool> {
549 if assets.len() == 0 {
550 return Err(revert("Assets resolution failure."));
551 }
552
553 let dest = Self::redirect_relay_to_asset_hub(&assets, dest);
554 Self::ensure_dot_transfer_policy(assets.inner(), &dest)?;
555
556 let (assets_transfer_type, fees_transfer_type, fee_asset_id) =
557 Self::resolve_transfer_types(&assets, fee_index, &dest)?;
558
559 log::trace!(target: "xcm-precompile:transfer", "assets: {:?}, dest: {:?}, beneficiary: {:?}, transfer types: {:?}/{:?}", assets, dest, beneficiary, assets_transfer_type, fees_transfer_type);
560
561 let call = pallet_xcm::Call::<Runtime>::transfer_assets_using_type_and_then {
562 dest: Box::new(VersionedLocation::V5(dest)),
563 assets: Box::new(VersionedAssets::V5(assets.clone())),
564 assets_transfer_type: Box::new(assets_transfer_type),
565 remote_fees_id: Box::new(VersionedAssetId::V5(fee_asset_id)),
566 fees_transfer_type: Box::new(fees_transfer_type),
567 custom_xcm_on_dest: Box::new(VersionedXcm::V5(Self::deposit_to_beneficiary(
568 assets.len() as u32,
569 beneficiary,
570 ))),
571 weight_limit,
572 };
573
574 let origin = Some(Runtime::AddressMapping::into_account_id(
575 handle.context().caller,
576 ))
577 .into();
578
579 RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
580
581 Ok(true)
582 }
583
584 fn redirect_relay_to_asset_hub(assets: &Assets, dest: Location) -> Location {
587 let local_asset_present = assets
588 .inner()
589 .iter()
590 .any(|asset| asset.id.0 == Location::here());
591
592 if dest == Location::parent() && local_asset_present {
593 Location::new(1, [Junction::Parachain(ASSET_HUB_PARA_ID)])
594 } else {
595 dest
596 }
597 }
598
599 fn chain_part(is_relay: bool, parachain_id: U256) -> EvmResult<Location> {
601 if is_relay {
602 return Ok(Location::parent());
603 }
604
605 let parachain_id: u32 = parachain_id
606 .try_into()
607 .map_err(|_| revert("error converting parachain_id, maybe value too large"))?;
608
609 Ok(Junctions::from(Junction::Parachain(parachain_id)).into_exterior(1))
610 }
611
612 fn asset_location(address: H160, native_address: bool) -> Option<Location> {
617 if native_address && address == NATIVE_ADDRESS {
618 return Some(Location::here());
619 }
620
621 Runtime::address_to_asset_id(address).and_then(|id| C::convert_back(&id))
622 }
623
624 fn amount(amount: U256) -> EvmResult<u128> {
625 let amount: u128 = amount
626 .try_into()
627 .map_err(|_| revert("error converting amount, maybe value too large"))?;
628
629 if amount == 0 {
630 return Err(revert("amount must be greater than zero"));
631 }
632
633 Ok(amount)
634 }
635
636 fn total_with_fee(amount_of_tokens: U256, fee: U256) -> EvmResult<U256> {
637 amount_of_tokens
638 .checked_add(fee)
639 .ok_or(revert("error adding fee to amount, maybe value too large"))
640 }
641
642 fn weight_limit(weight: &WeightV2) -> EvmResult<WeightLimit> {
648 match (weight.ref_time, weight.proof_size) {
649 (0, 0) => Ok(WeightLimit::Unlimited),
650 (0, _) => Err(revert(
651 "weight.ref_time is zero but weight.proof_size is not: pass (0, 0) for an \
652 unlimited weight limit",
653 )),
654 (_, 0) => Err(revert(
655 "weight.proof_size is zero: every destination weighs a message with a non-zero \
656 proof size, so the transfer would be rejected there as overweight",
657 )),
658 (ref_time, proof_size) => Ok(WeightLimit::Limited(Weight::from_parts(
659 ref_time, proof_size,
660 ))),
661 }
662 }
663
664 fn fee_index_after_sort(unsorted: &[Asset], sorted: &Assets, fee_item: u32) -> EvmResult<u32> {
668 let fee_asset_id = unsorted
669 .get(fee_item as usize)
670 .ok_or(revert("fee_index is out of bounds of the assets list"))?
671 .id
672 .clone();
673
674 sorted
675 .inner()
676 .iter()
677 .position(|asset| asset.id == fee_asset_id)
678 .map(|index| index as u32)
679 .ok_or(revert("fee_index is out of bounds of the assets list"))
680 }
681
682 fn beneficiary_32(recipient_account_id: H256) -> Location {
684 Junction::AccountId32 {
685 network: None,
686 id: recipient_account_id.into(),
687 }
688 .into()
689 }
690
691 fn beneficiary_key_20(recipient_account_id: Address) -> Location {
693 Junction::AccountKey20 {
694 network: None,
695 key: recipient_account_id.0.to_fixed_bytes(),
696 }
697 .into()
698 }
699
700 fn resolve_transfer_types(
703 assets: &Assets,
704 fee_asset_item: u32,
705 dest: &Location,
706 ) -> EvmResult<(TransferType, TransferType, AssetId)> {
707 let assets = assets.inner();
708 let fee_asset = assets
709 .get(fee_asset_item as usize)
710 .ok_or(revert("fee_index is out of bounds of the assets list"))?;
711
712 let resolve = |asset: &Asset| {
713 resolve_transfer_type::<<Runtime as pallet_xcm::Config>::XcmExecutor>(asset, dest)
714 .ok_or(revert("cannot determine the reserve location for asset"))
715 };
716
717 let fees_transfer_type = resolve(fee_asset)?;
718
719 let mut assets_transfer_type = None;
720 for (idx, asset) in assets.iter().enumerate() {
721 if idx == fee_asset_item as usize {
722 continue;
723 }
724 let transfer_type = resolve(asset)?;
725 match &assets_transfer_type {
726 Some(existing) if existing != &transfer_type => {
727 return Err(revert("all non-fee assets must share the same reserve"))
728 }
729 Some(_) => {}
730 None => assets_transfer_type = Some(transfer_type),
731 }
732 }
733
734 let assets_transfer_type =
736 assets_transfer_type.unwrap_or_else(|| fees_transfer_type.clone());
737
738 Ok((
739 assets_transfer_type,
740 fees_transfer_type,
741 fee_asset.id.clone(),
742 ))
743 }
744
745 fn deposit_to_beneficiary(assets_count: u32, beneficiary: Location) -> Xcm<()> {
748 Xcm(vec![DepositAsset {
749 assets: Wild(AllCounted(assets_count)),
750 beneficiary,
751 }])
752 }
753
754 fn ensure_dot_transfer_policy(assets: &[Asset], dest_chain: &Location) -> EvmResult<()> {
761 if dest_chain != &Location::parent() {
762 return Ok(());
763 }
764
765 let deprecated_dot_location = Location::new(1, Junctions::Here);
766
767 for asset in assets {
768 let AssetId(location) = &asset.id;
769 if location == &deprecated_dot_location {
770 return Err(revert(
771 "DOT cannot be sent directly to the relay. \
772 Route via AssetHub (parachain 1000).",
773 ));
774 }
775 }
776
777 Ok(())
778 }
779}
780
781#[derive(Debug, Clone, solidity::Codec)]
782pub struct WeightV2 {
783 ref_time: u64,
784 proof_size: u64,
785}
786
787impl WeightV2 {
788 pub fn from(ref_time: u64, proof_size: u64) -> Self {
789 WeightV2 {
790 ref_time,
791 proof_size,
792 }
793 }
794
795 pub fn get_weight(&self) -> Weight {
796 Weight::from_parts(self.ref_time, self.proof_size)
797 }
798}
799
800#[derive(Debug, Clone, solidity::Codec)]
801pub struct Currency {
802 address: Address,
803 amount: U256,
804}
805
806impl Currency {
807 pub fn get_address(&self) -> Address {
808 self.address
809 }
810
811 pub fn get_amount(&self) -> U256 {
812 self.amount
813 }
814}
815
816impl From<(Address, U256)> for Currency {
817 fn from(tuple: (Address, U256)) -> Self {
818 Currency {
819 address: tuple.0,
820 amount: tuple.1,
821 }
822 }
823}
824
825#[derive(Debug, Clone, solidity::Codec)]
826pub struct EvmMultiAsset {
827 location: Location,
828 amount: U256,
829}
830
831impl From<(Location, U256)> for EvmMultiAsset {
832 fn from(tuple: (Location, U256)) -> Self {
833 EvmMultiAsset {
834 location: tuple.0,
835 amount: tuple.1,
836 }
837 }
838}
839
840impl EvmMultiAsset {
841 pub fn get_location(&self) -> Location {
842 self.location.clone()
843 }
844
845 pub fn get_amount(&self) -> U256 {
846 self.amount
847 }
848}