1
// Copyright 2019-2025 PureStake Inc.
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
use super::*;
18
use frame_support::{
19
	ensure,
20
	storage::types::{StorageDoubleMap, ValueQuery},
21
	traits::{StorageInstance, Time},
22
	Blake2_128Concat,
23
};
24
use pallet_assets::pallet::{
25
	Instance1, Instance10, Instance11, Instance12, Instance13, Instance14, Instance15, Instance16,
26
	Instance2, Instance3, Instance4, Instance5, Instance6, Instance7, Instance8, Instance9,
27
};
28
use scale_info::prelude::string::ToString;
29
use sp_core::H256;
30
use sp_io::hashing::keccak_256;
31
use sp_runtime::traits::UniqueSaturatedInto;
32

            
33
/// EIP2612 permit typehash.
34
pub const PERMIT_TYPEHASH: [u8; 32] = keccak256!(
35
	"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
36
);
37

            
38
/// EIP2612 permit domain used to compute an individualized domain separator.
39
const PERMIT_DOMAIN: [u8; 32] = keccak256!(
40
	"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
41
);
42

            
43
/// Associates pallet Instance to a prefix used for the Nonces storage.
44
/// This trait is implemented for () and the 16 substrate Instance.
45
pub trait InstanceToPrefix {
46
	/// Prefix used for the Approves storage.
47
	type NoncesPrefix: StorageInstance;
48
}
49

            
50
// We use a macro to implement the trait for () and the 16 substrate Instance.
51
macro_rules! impl_prefix {
52
	($instance:ident, $name:literal) => {
53
		// Using `paste!` we generate a dedicated module to avoid collisions
54
		// between each instance `Nonces` struct.
55
		paste::paste! {
56
			mod [<_impl_prefix_ $instance:snake>] {
57
				use super::*;
58

            
59
				pub struct Nonces;
60

            
61
				impl StorageInstance for Nonces {
62
					const STORAGE_PREFIX: &'static str = "Nonces";
63

            
64
18
					fn pallet_prefix() -> &'static str {
65
18
						$name
66
18
					}
67
				}
68

            
69
				impl InstanceToPrefix for $instance {
70
					type NoncesPrefix = Nonces;
71
				}
72
			}
73
		}
74
	};
75
}
76

            
77
// Since the macro expect a `ident` to be used with `paste!` we cannot provide `()` directly.
78
type Instance0 = ();
79

            
80
impl_prefix!(Instance0, "Erc20Instance0Assets");
81
impl_prefix!(Instance1, "Erc20Instance1Assets");
82
impl_prefix!(Instance2, "Erc20Instance2Assets");
83
impl_prefix!(Instance3, "Erc20Instance3Assets");
84
impl_prefix!(Instance4, "Erc20Instance4Assets");
85
impl_prefix!(Instance5, "Erc20Instance5Assets");
86
impl_prefix!(Instance6, "Erc20Instance6Assets");
87
impl_prefix!(Instance7, "Erc20Instance7Assets");
88
impl_prefix!(Instance8, "Erc20Instance8Assets");
89
impl_prefix!(Instance9, "Erc20Instance9Assets");
90
impl_prefix!(Instance10, "Erc20Instance10Assets");
91
impl_prefix!(Instance11, "Erc20Instance11Assets");
92
impl_prefix!(Instance12, "Erc20Instance12Assets");
93
impl_prefix!(Instance13, "Erc20Instance13Assets");
94
impl_prefix!(Instance14, "Erc20Instance14Assets");
95
impl_prefix!(Instance15, "Erc20Instance15Assets");
96
impl_prefix!(Instance16, "Erc20Instance16Assets");
97

            
98
/// Storage type used to store EIP2612 nonces.
99
pub type NoncesStorage<Instance> = StorageDoubleMap<
100
	<Instance as InstanceToPrefix>::NoncesPrefix,
101
	// Asset contract address
102
	Blake2_128Concat,
103
	H160,
104
	// Owner
105
	Blake2_128Concat,
106
	H160,
107
	// Nonce
108
	U256,
109
	ValueQuery,
110
>;
111

            
112
pub struct Eip2612<Runtime, Instance: 'static = ()>(PhantomData<(Runtime, Instance)>);
113

            
114
impl<Runtime, Instance> Eip2612<Runtime, Instance>
115
where
116
	Instance: InstanceToPrefix + 'static,
117
	Runtime: pallet_assets::Config<Instance> + pallet_evm::Config + frame_system::Config,
118
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
119
	Runtime::RuntimeCall: From<pallet_assets::Call<Runtime, Instance>>,
120
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
121
	BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256> + solidity::Codec,
122
	Runtime: AccountIdAssetIdConversion<Runtime::AccountId, AssetIdOf<Runtime, Instance>>,
123
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: OriginTrait,
124
	AssetIdOf<Runtime, Instance>: Display,
125
	Runtime::AccountId: Into<H160>,
126
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
127
{
128
10
	fn compute_domain_separator(address: H160, asset_id: AssetIdOf<Runtime, Instance>) -> [u8; 32] {
129
10
		let asset_name = pallet_assets::Pallet::<Runtime, Instance>::name(asset_id.clone());
130

            
131
10
		let name = if asset_name.is_empty() {
132
8
			let mut name = b"Unnamed XC20 #".to_vec();
133
8
			name.extend_from_slice(asset_id.to_string().as_bytes());
134
8
			name
135
		} else {
136
2
			asset_name
137
		};
138

            
139
10
		let name: H256 = keccak_256(&name).into();
140
10
		let version: H256 = keccak256!("1").into();
141
10
		let chain_id: U256 = Runtime::ChainId::get().into();
142
10

            
143
10
		let domain_separator_inner = solidity::encode_arguments((
144
10
			H256::from(PERMIT_DOMAIN),
145
10
			name,
146
10
			version,
147
10
			chain_id,
148
10
			Address(address),
149
10
		));
150
10

            
151
10
		keccak_256(&domain_separator_inner)
152
10
	}
153

            
154
9
	pub fn generate_permit(
155
9
		address: H160,
156
9
		asset_id: AssetIdOf<Runtime, Instance>,
157
9
		owner: H160,
158
9
		spender: H160,
159
9
		value: U256,
160
9
		nonce: U256,
161
9
		deadline: U256,
162
9
	) -> [u8; 32] {
163
9
		let domain_separator = Self::compute_domain_separator(address, asset_id);
164
9

            
165
9
		let permit_content = solidity::encode_arguments((
166
9
			H256::from(PERMIT_TYPEHASH),
167
9
			Address(owner),
168
9
			Address(spender),
169
9
			value,
170
9
			nonce,
171
9
			deadline,
172
9
		));
173
9
		let permit_content = keccak_256(&permit_content);
174
9

            
175
9
		let mut pre_digest = Vec::with_capacity(2 + 32 + 32);
176
9
		pre_digest.extend_from_slice(b"\x19\x01");
177
9
		pre_digest.extend_from_slice(&domain_separator);
178
9
		pre_digest.extend_from_slice(&permit_content);
179
9
		keccak_256(&pre_digest)
180
9
	}
181

            
182
	// Translated from
183
	// https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol#L81
184
	#[allow(clippy::too_many_arguments)]
185
6
	pub(crate) fn permit(
186
6
		asset_id: AssetIdOf<Runtime, Instance>,
187
6
		handle: &mut impl PrecompileHandle,
188
6
		owner: Address,
189
6
		spender: Address,
190
6
		value: U256,
191
6
		deadline: U256,
192
6
		v: u8,
193
6
		r: H256,
194
6
		s: H256,
195
6
	) -> EvmResult {
196
6
		// NoncesStorage: Blake2_128(16) + contract(20) + Blake2_128(16) + owner(20) + nonce(32)
197
6
		handle.record_db_read::<Runtime>(104)?;
198

            
199
6
		let owner: H160 = owner.into();
200
6
		let spender: H160 = spender.into();
201
6

            
202
6
		let address = handle.code_address();
203
6

            
204
6
		// Blockchain time is in ms while Ethereum use second timestamps.
205
6
		let timestamp: u128 =
206
6
			<Runtime as pallet_evm::Config>::Timestamp::now().unique_saturated_into();
207
6
		let timestamp: U256 = U256::from(timestamp / 1000);
208
6

            
209
6
		ensure!(deadline >= timestamp, revert("Permit expired"));
210

            
211
5
		let nonce = NoncesStorage::<Instance>::get(address, owner);
212
5

            
213
5
		let permit = Self::generate_permit(
214
5
			address,
215
5
			asset_id.clone(),
216
5
			owner,
217
5
			spender,
218
5
			value,
219
5
			nonce,
220
5
			deadline,
221
5
		);
222
5

            
223
5
		let mut sig = [0u8; 65];
224
5
		sig[0..32].copy_from_slice(r.as_bytes());
225
5
		sig[32..64].copy_from_slice(s.as_bytes());
226
5
		sig[64] = v;
227

            
228
5
		let signer = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &permit)
229
5
			.map_err(|_| revert("Invalid permit"))?;
230
5
		let signer = H160::from(H256::from_slice(keccak_256(&signer).as_slice()));
231
5

            
232
5
		ensure!(
233
5
			signer != H160::zero() && signer == owner,
234
2
			revert("Invalid permit")
235
		);
236

            
237
3
		NoncesStorage::<Instance>::insert(address, owner, nonce + U256::one());
238
3

            
239
3
		Erc20AssetsPrecompileSet::<Runtime, Instance>::approve_inner(
240
3
			asset_id, handle, owner, spender, value,
241
3
		)?;
242

            
243
3
		log3(
244
3
			address,
245
3
			SELECTOR_LOG_APPROVAL,
246
3
			owner,
247
3
			spender,
248
3
			solidity::encode_event_data(value),
249
3
		)
250
3
		.record(handle)?;
251

            
252
3
		Ok(())
253
6
	}
254

            
255
10
	pub(crate) fn nonces(
256
10
		_asset_id: AssetIdOf<Runtime, Instance>,
257
10
		handle: &mut impl PrecompileHandle,
258
10
		owner: Address,
259
10
	) -> EvmResult<U256> {
260
10
		// NoncesStorage: Blake2_128(16) + contract(20) + Blake2_128(16) + owner(20) + nonce(32)
261
10
		handle.record_db_read::<Runtime>(104)?;
262

            
263
10
		let owner: H160 = owner.into();
264
10

            
265
10
		let nonce = NoncesStorage::<Instance>::get(handle.code_address(), owner);
266
10

            
267
10
		Ok(nonce)
268
10
	}
269

            
270
1
	pub(crate) fn domain_separator(
271
1
		asset_id: AssetIdOf<Runtime, Instance>,
272
1
		handle: &mut impl PrecompileHandle,
273
1
	) -> EvmResult<H256> {
274
1
		// Storage item: AssetMetadata:
275
1
		// Blake2_128(16) + AssetId(16) + AssetMetadata[deposit(16) + name(StringLimit)
276
1
		// + symbol(StringLimit) + decimals(1) + is_frozen(1)]
277
1
		handle.record_db_read::<Runtime>(
278
1
			50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
279
1
		)?;
280

            
281
1
		let domain_separator: H256 =
282
1
			Self::compute_domain_separator(handle.code_address(), asset_id).into();
283
1

            
284
1
		Ok(domain_separator)
285
1
	}
286
}