1
// Copyright 2025 Moonbeam Foundation.
2
// This file is part of Moonbeam.
3

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

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

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

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

            
19
use core::fmt::Display;
20
use core::marker::PhantomData;
21
use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
22
use frame_support::traits::OriginTrait;
23
use moonkit_xcm_primitives::AccountIdAssetIdConversion;
24
use pallet_evm::AddressMapping;
25
use precompile_utils::prelude::*;
26
use sp_core::{MaxEncodedLen, H160, U256};
27
use sp_runtime::traits::{Dispatchable, StaticLookup};
28
use xcm::latest::prelude::Location;
29

            
30
pub type ForeignAssetInstance = ();
31

            
32
/// Alias for the Balance type for old foreign assets
33
pub type BalanceOf<Runtime> = <Runtime as pallet_assets::Config<ForeignAssetInstance>>::Balance;
34

            
35
/// Alias for the Asset Id type for old foreign assets
36
pub type AssetIdOf<Runtime> = <Runtime as pallet_assets::Config<ForeignAssetInstance>>::AssetId;
37

            
38
pub struct ForeignAssetMigratorPrecompile<Runtime>(PhantomData<Runtime>);
39

            
40
impl<R> Clone for ForeignAssetMigratorPrecompile<R> {
41
	fn clone(&self) -> Self {
42
		Self(PhantomData)
43
	}
44
}
45

            
46
impl<R> Default for ForeignAssetMigratorPrecompile<R> {
47
	fn default() -> Self {
48
		Self(PhantomData)
49
	}
50
}
51

            
52
impl<Runtime> ForeignAssetMigratorPrecompile<Runtime> {
53
	pub fn new() -> Self {
54
		Self(PhantomData)
55
	}
56
}
57

            
58
4
#[precompile_utils::precompile]
59
impl<Runtime> ForeignAssetMigratorPrecompile<Runtime>
60
where
61
	Runtime: pallet_asset_manager::Config<AssetId = u128>
62
		+ pallet_assets::Config<ForeignAssetInstance, AssetId = u128>
63
		+ pallet_evm::Config
64
		+ pallet_moonbeam_foreign_assets::Config
65
		+ frame_system::Config,
66
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
67
	Runtime::RuntimeCall: From<pallet_assets::Call<Runtime, ForeignAssetInstance>>,
68
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
69
	BalanceOf<Runtime>: TryFrom<U256> + Into<U256> + solidity::Codec,
70
	Runtime: AccountIdAssetIdConversion<Runtime::AccountId, AssetIdOf<Runtime>>,
71
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: OriginTrait,
72
	AssetIdOf<Runtime>: Display,
73
	Runtime::AccountId: Into<H160>,
74
	<Runtime as pallet_asset_manager::Config>::ForeignAssetType: Into<Option<Location>>,
75
	Runtime::AddressMapping: AddressMapping<Runtime::AccountId>,
76
{
77
	#[precompile::public("migrateAccounts(address,uint32)")]
78
	fn migrate_accounts(
79
		handle: &mut impl PrecompileHandle,
80
		asset_address: Address,
81
		n: u32,
82
	) -> EvmResult<U256> {
83
		let new_asset_id = Self::get_new_asset_id_with_same_xcm_location(handle, asset_address)?;
84

            
85
		// Account proof size for N accounts keys
86
		// Storage item: Account
87
		// Key1: Blake2_128(16) + AssetId(16)
88
		// Key2: Blake2_128(16) + AccountId(20)
89
		handle.record_db_read::<Runtime>(68 * n as usize)?;
90

            
91
		// Get N account ids
92
		// IMPORTANT: we can't mutate storage while iterating, that's why we collect N accounts ids
93
		// in a Vec BEFORE processing them!
94
		let accounts_ids =
95
			pallet_assets::pallet::Account::<Runtime, ForeignAssetInstance>::iter_key_prefix(
96
				new_asset_id,
97
			)
98
			.take(n as usize)
99
			.collect::<sp_std::vec::Vec<_>>();
100

            
101
		// Refound gas if there is less than N accounts
102
		if accounts_ids.len() < n as usize {
103
			let n_diff = ((n as usize) - accounts_ids.len()) as u64;
104
			handle.refund_external_cost(
105
				Some(n_diff * RuntimeHelper::<Runtime>::db_read_gas_cost()),
106
				Some(n_diff * 68),
107
			);
108
		}
109

            
110
		// IMPORTANT: we can't mutate storage while iterating, that's why we collect N accounts ids
111
		// in a Vec BEFORE processing them!
112
		let mut counter = 0;
113
		for account_id in accounts_ids {
114
			Self::migrate_account_inner(handle, new_asset_id, account_id.into())?;
115
			counter += 1;
116
		}
117

            
118
		Ok(U256([counter, 0, 0, 0]))
119
	}
120

            
121
	#[precompile::public("migrateAccount(address,address)")]
122
	fn migrate_account(
123
		handle: &mut impl PrecompileHandle,
124
		asset_address: Address,
125
		account: Address,
126
	) -> EvmResult<()> {
127
		let new_asset_id = Self::get_new_asset_id_with_same_xcm_location(handle, asset_address)?;
128

            
129
		Self::migrate_account_inner(handle, new_asset_id, account.into())?;
130

            
131
		Ok(())
132
	}
133

            
134
	fn get_new_asset_id_with_same_xcm_location(
135
		handle: &mut impl PrecompileHandle,
136
		asset_address: Address,
137
	) -> EvmResult<u128> {
138
		let asset_address: H160 = asset_address.into();
139

            
140
		let asset_address = Runtime::AddressMapping::into_account_id(asset_address);
141

            
142
		// compute asset id from asset address
143
		let asset_id = match Runtime::account_to_asset_id(asset_address) {
144
			Some((_, asset_id)) => asset_id,
145
			None => return Err(revert("invalid asset address")),
146
		};
147

            
148
		// Storage item: AssetIdType
149
		// key:  Blake2_128(16) + AssetId(16)
150
		// value: XCMv3 Location
151
		handle.record_db_read::<Runtime>(32 + xcm::v3::Location::max_encoded_len())?;
152

            
153
		// Get asset XCM location
154
		let asset_type = pallet_asset_manager::Pallet::<Runtime>::asset_id_type(&asset_id)
155
			.ok_or(revert("asset id doesn't exist"))?;
156
		let xcm_location = asset_type.into().ok_or(revert("invalid XCM Location"))?;
157

            
158
		// Storage item: AssetsByLocation
159
		// key:  Blake2_128(16) + XCM latest Location
160
		// value: AssetId(16) + AssetStatus(1)
161
		handle.record_db_read::<Runtime>(33 + Location::max_encoded_len())?;
162

            
163
		// Get new asset id
164
		let (new_asset_id, _) =
165
			pallet_moonbeam_foreign_assets::Pallet::<Runtime>::assets_by_location(&xcm_location)
166
				.ok_or(revert("new foreign asset doesn't exist"))?;
167

            
168
		Ok(new_asset_id)
169
	}
170

            
171
	fn migrate_account_inner(
172
		handle: &mut impl PrecompileHandle,
173
		new_asset_id: u128,
174
		account: H160,
175
	) -> EvmResult<()> {
176
		let account_id = Runtime::AddressMapping::into_account_id(account);
177

            
178
		// Storage item: Account
179
		// Key1: Blake2_128(16) + AssetId(16)
180
		// key2: Blake2_128(16) + AccountId(20)
181
		// Value: AssetAccount(19 + Extra)
182
		handle.record_db_read::<Runtime>(
183
			87 + <Runtime as pallet_assets::Config<ForeignAssetInstance>>::Extra::max_encoded_len(),
184
		)?;
185

            
186
		// Get old asset balance
187
		let amount = pallet_assets::Pallet::<Runtime, ForeignAssetInstance>::balance(
188
			new_asset_id,
189
			&account_id,
190
		);
191

            
192
		// Burn account balance on hold foreign asset
193
		RuntimeHelper::<Runtime>::try_dispatch(
194
			handle,
195
			Some(pallet_asset_manager::Pallet::<Runtime>::account_id()).into(),
196
			pallet_assets::Call::<Runtime, ForeignAssetInstance>::burn {
197
				id: new_asset_id.into(),
198
				who: Runtime::Lookup::unlookup(account_id.clone()),
199
				amount,
200
			},
201
			0,
202
		)?;
203

            
204
		// Mint same balance for new asset
205
		pallet_moonbeam_foreign_assets::Pallet::<Runtime>::mint_into(
206
			new_asset_id,
207
			account_id,
208
			amount.into(),
209
		)
210
		.map_err(|_| revert("fail to mint new foreign asset"))?;
211

            
212
		Ok(())
213
	}
214
}