1
// Copyright 2024 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
{
76
	#[precompile::public("migrateAccounts(address,uint32)")]
77
	fn migrate_accounts(
78
		handle: &mut impl PrecompileHandle,
79
		asset_address: Address,
80
		n: u32,
81
	) -> EvmResult<U256> {
82
		let new_asset_id = Self::get_new_asset_id_with_same_xcm_location(handle, asset_address)?;
83

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

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

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

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

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

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

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

            
130
		Ok(())
131
	}
132

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

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

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

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

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

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

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

            
167
		Ok(new_asset_id)
168
	}
169

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

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

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

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

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

            
211
		Ok(())
212
	}
213
}