1
// Copyright 2019-2022 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
{
127
10
	fn compute_domain_separator(address: H160, asset_id: AssetIdOf<Runtime, Instance>) -> [u8; 32] {
128
10
		let asset_name = pallet_assets::Pallet::<Runtime, Instance>::name(asset_id.clone());
129

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

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

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

            
150
10
		keccak_256(&domain_separator_inner).into()
151
10
	}
152

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

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

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

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

            
197
6
		let owner: H160 = owner.into();
198
6
		let spender: H160 = spender.into();
199
6

            
200
6
		let address = handle.code_address();
201
6

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

            
207
6
		ensure!(deadline >= timestamp, revert("Permit expired"));
208

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

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

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

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

            
230
4
		ensure!(
231
4
			signer != H160::zero() && signer == owner,
232
1
			revert("Invalid permit")
233
		);
234

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

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

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

            
250
3
		Ok(())
251
6
	}
252

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

            
261
10
		let owner: H160 = owner.into();
262
10

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

            
265
10
		Ok(nonce)
266
10
	}
267

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

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

            
282
1
		Ok(domain_separator)
283
1
	}
284
}