referrerpolicy=no-referrer-when-downgrade

pallet_revive/
address.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Functions that deal contract addresses.
19
20use crate::{ensure, Config, Error, HoldReason, OriginalAccount};
21use alloc::vec::Vec;
22use core::marker::PhantomData;
23use frame_support::traits::{fungible::MutateHold, tokens::Precision};
24use sp_core::{Get, H160};
25use sp_io::hashing::keccak_256;
26use sp_runtime::{AccountId32, DispatchResult, Saturating};
27
28/// Map between the native chain account id `T` and an Ethereum [`H160`].
29///
30/// This trait exists only to emulate specialization for different concrete
31/// native account ids. **Not** to make the mapping user configurable. Hence
32/// the trait is `Sealed` and depending on your runtime configuration you need
33/// to pick either [`AccountId32Mapper`] or [`H160Mapper`]. Picking the wrong
34/// one will result in a compilation error. No footguns here.
35///
36/// Please note that we assume that the native account is at least 20 bytes and
37/// only implement this type for a `T` where this is the case. Luckily, this is the
38/// case for all existing runtimes as of right now. Reasoning is that this will allow
39/// us to reverse an address -> account_id mapping by just stripping the prefix.
40///
41/// We require the mapping to be reversible. Since we are potentially dealing with types of
42/// different sizes one direction of the mapping is necessarily lossy. This requires the mapping to
43/// make use of the [`OriginalAccount`] storage item to reverse the mapping.
44pub trait AddressMapper<T: Config>: private::Sealed {
45	/// Convert an account id to an ethereum address.
46	fn to_address(account_id: &T::AccountId) -> H160;
47
48	/// Convert an ethereum address to a native account id.
49	fn to_account_id(address: &H160) -> T::AccountId;
50
51	/// Same as [`Self::to_account_id`] but always returns the fallback account.
52	///
53	/// This skips the query into [`OriginalAccount`] and always returns the stateless
54	/// fallback account. This is useful when we know for a fact that the `address`
55	/// in question is originally a `H160`. This is usually only the case when we
56	/// generated a new contract address.
57	fn to_fallback_account_id(address: &H160) -> T::AccountId;
58
59	/// Create a stateful mapping for `account_id`
60	///
61	/// This will enable `to_account_id` to map back to the original
62	/// `account_id` instead of the fallback account id.
63	fn map(account_id: &T::AccountId) -> DispatchResult;
64
65	/// Map an account id without taking any deposit.
66	/// This is only useful for genesis configuration, or benchmarks.
67	fn map_no_deposit(account_id: &T::AccountId) -> DispatchResult {
68		Self::map(account_id)
69	}
70
71	/// Remove the mapping in order to reclaim the deposit.
72	///
73	/// There is no reason why one would unmap their `account_id` except
74	/// for reclaiming the deposit.
75	fn unmap(account_id: &T::AccountId) -> DispatchResult;
76
77	/// Returns true if the `account_id` is usable as an origin.
78	///
79	/// This means either the `account_id` doesn't require a stateful mapping
80	/// or a stateful mapping exists.
81	fn is_mapped(account_id: &T::AccountId) -> bool;
82}
83
84mod private {
85	pub trait Sealed {}
86	impl<T> Sealed for super::AccountId32Mapper<T> {}
87	impl<T> Sealed for super::H160Mapper<T> {}
88	impl<T> Sealed for super::TestAccountMapper<T> {}
89}
90
91/// The mapper to be used if the account id is `AccountId32`.
92///
93/// It converts between addresses by either hash then truncate the last 12 bytes or
94/// suffixing them. To recover the original account id of a hashed and truncated account id we use
95/// [`OriginalAccount`] and will fall back to all `0xEE` if account was found. This means contracts
96/// and plain wallets controlled by an `secp256k1` always have a `0xEE` suffixed account.
97pub struct AccountId32Mapper<T>(PhantomData<T>);
98
99/// The mapper to be used if the account id is `H160`.
100///
101/// It just trivially returns its inputs and doesn't make use of any state.
102pub struct H160Mapper<T>(PhantomData<T>);
103
104/// An account mapper that can be used for testing u64 account ids.
105pub struct TestAccountMapper<T>(PhantomData<T>);
106
107impl<T> AddressMapper<T> for AccountId32Mapper<T>
108where
109	T: Config<AccountId = AccountId32>,
110{
111	fn to_address(account_id: &AccountId32) -> H160 {
112		let account_bytes: &[u8; 32] = account_id.as_ref();
113		if is_eth_derived(account_id) {
114			// this was originally an eth address
115			// we just strip the 0xEE suffix to get the original address
116			H160::from_slice(&account_bytes[..20])
117		} else {
118			// this is an (ed|sr)25510 derived address
119			// avoid truncating the public key by hashing it first
120			let account_hash = keccak_256(account_bytes);
121			H160::from_slice(&account_hash[12..])
122		}
123	}
124
125	fn to_account_id(address: &H160) -> AccountId32 {
126		<OriginalAccount<T>>::get(address).unwrap_or_else(|| Self::to_fallback_account_id(address))
127	}
128
129	fn to_fallback_account_id(address: &H160) -> AccountId32 {
130		let mut account_id = AccountId32::new([0xEE; 32]);
131		let account_bytes: &mut [u8; 32] = account_id.as_mut();
132		account_bytes[..20].copy_from_slice(address.as_bytes());
133		account_id
134	}
135
136	fn map(account_id: &T::AccountId) -> DispatchResult {
137		ensure!(!Self::is_mapped(account_id), <Error<T>>::AccountAlreadyMapped);
138
139		// each mapping entry stores the address (20 bytes) and the account id (32 bytes)
140		let deposit = T::DepositPerByte::get()
141			.saturating_mul(52u32.into())
142			.saturating_add(T::DepositPerItem::get());
143		T::Currency::hold(&HoldReason::AddressMapping.into(), account_id, deposit)?;
144
145		<OriginalAccount<T>>::insert(Self::to_address(account_id), account_id);
146		Ok(())
147	}
148
149	fn map_no_deposit(account_id: &T::AccountId) -> DispatchResult {
150		ensure!(!Self::is_mapped(account_id), <Error<T>>::AccountAlreadyMapped);
151		<OriginalAccount<T>>::insert(Self::to_address(account_id), account_id);
152		Ok(())
153	}
154
155	fn unmap(account_id: &T::AccountId) -> DispatchResult {
156		// will do nothing if address is not mapped so no check required
157		<OriginalAccount<T>>::remove(Self::to_address(account_id));
158		T::Currency::release_all(
159			&HoldReason::AddressMapping.into(),
160			account_id,
161			Precision::BestEffort,
162		)?;
163		Ok(())
164	}
165
166	fn is_mapped(account_id: &T::AccountId) -> bool {
167		is_eth_derived(account_id) ||
168			<OriginalAccount<T>>::contains_key(Self::to_address(account_id))
169	}
170}
171
172impl<T> AddressMapper<T> for TestAccountMapper<T>
173where
174	T: Config<AccountId = u64>,
175{
176	fn to_address(account_id: &T::AccountId) -> H160 {
177		let mut bytes = [0u8; 20];
178		bytes[12..].copy_from_slice(&account_id.to_be_bytes());
179		H160::from(bytes)
180	}
181
182	fn to_account_id(address: &H160) -> T::AccountId {
183		Self::to_fallback_account_id(address)
184	}
185
186	fn to_fallback_account_id(address: &H160) -> T::AccountId {
187		u64::from_be_bytes(address.as_ref()[12..].try_into().unwrap())
188	}
189
190	fn map(_account_id: &T::AccountId) -> DispatchResult {
191		Ok(())
192	}
193
194	fn unmap(_account_id: &T::AccountId) -> DispatchResult {
195		Ok(())
196	}
197
198	fn is_mapped(_account_id: &T::AccountId) -> bool {
199		true
200	}
201}
202
203/// Returns true if the passed account id is controlled by an eth key.
204///
205/// This is a stateless check that just compares the last 12 bytes. Please note that it is
206/// theoretically possible to create an ed25519 keypair that passed this filter. However,
207/// this can't be used for an attack. It also won't happen by accident since everybody is using
208/// sr25519 where this is not a valid public key.
209pub fn is_eth_derived(account_id: &AccountId32) -> bool {
210	let account_bytes: &[u8; 32] = account_id.as_ref();
211	&account_bytes[20..] == &[0xEE; 12]
212}
213
214impl<T> AddressMapper<T> for H160Mapper<T>
215where
216	T: Config,
217	crate::AccountIdOf<T>: AsRef<[u8; 20]> + From<H160>,
218{
219	fn to_address(account_id: &T::AccountId) -> H160 {
220		H160::from_slice(account_id.as_ref())
221	}
222
223	fn to_account_id(address: &H160) -> T::AccountId {
224		Self::to_fallback_account_id(address)
225	}
226
227	fn to_fallback_account_id(address: &H160) -> T::AccountId {
228		(*address).into()
229	}
230
231	fn map(_account_id: &T::AccountId) -> DispatchResult {
232		Ok(())
233	}
234
235	fn unmap(_account_id: &T::AccountId) -> DispatchResult {
236		Ok(())
237	}
238
239	fn is_mapped(_account_id: &T::AccountId) -> bool {
240		true
241	}
242}
243
244/// Determine the address of a contract using CREATE semantics.
245pub fn create1(deployer: &H160, nonce: u64) -> H160 {
246	let mut list = rlp::RlpStream::new_list(2);
247	list.append(&deployer.as_bytes());
248	list.append(&nonce);
249	let hash = keccak_256(&list.out());
250	H160::from_slice(&hash[12..])
251}
252
253/// Determine the address of a contract using the CREATE2 semantics.
254pub fn create2(deployer: &H160, code: &[u8], input_data: &[u8], salt: &[u8; 32]) -> H160 {
255	let init_code_hash = {
256		let init_code: Vec<u8> = code.into_iter().chain(input_data).cloned().collect();
257		keccak_256(init_code.as_ref())
258	};
259	let mut bytes = [0; 85];
260	bytes[0] = 0xff;
261	bytes[1..21].copy_from_slice(deployer.as_bytes());
262	bytes[21..53].copy_from_slice(salt);
263	bytes[53..85].copy_from_slice(&init_code_hash);
264	let hash = keccak_256(&bytes);
265	H160::from_slice(&hash[12..])
266}
267
268#[cfg(test)]
269mod test {
270	use super::*;
271	use crate::{
272		test_utils::*,
273		tests::{ExtBuilder, Test},
274		AddressMapper, Error,
275	};
276	use frame_support::{
277		assert_err,
278		traits::fungible::{InspectHold, Mutate},
279	};
280	use pretty_assertions::assert_eq;
281	use sp_core::{hex2array, H160};
282
283	#[test]
284	fn create1_works() {
285		assert_eq!(
286			create1(&ALICE_ADDR, 1u64),
287			H160(hex2array!("c851da37e4e8d3a20d8d56be2963934b4ad71c3b")),
288		)
289	}
290
291	#[test]
292	fn create2_works() {
293		assert_eq!(
294			create2(
295				&ALICE_ADDR,
296				&hex2array!("600060005560016000"),
297				&hex2array!("55"),
298				&hex2array!("1234567890123456789012345678901234567890123456789012345678901234")
299			),
300			H160(hex2array!("7f31e795e5836a19a8f919ab5a9de9a197ecd2b6")),
301		)
302	}
303
304	#[test]
305	fn fallback_map_works() {
306		assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
307		assert_eq!(
308			ALICE_FALLBACK,
309			<Test as Config>::AddressMapper::to_fallback_account_id(&ALICE_ADDR)
310		);
311		assert_eq!(ALICE_ADDR, <Test as Config>::AddressMapper::to_address(&ALICE_FALLBACK));
312	}
313
314	#[test]
315	fn map_works() {
316		ExtBuilder::default().build().execute_with(|| {
317			<Test as Config>::Currency::set_balance(&EVE, 1_000_000);
318			// before mapping the fallback account is returned
319			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
320			assert_eq!(EVE_FALLBACK, <Test as Config>::AddressMapper::to_account_id(&EVE_ADDR));
321			assert_eq!(
322				<Test as Config>::Currency::balance_on_hold(
323					&HoldReason::AddressMapping.into(),
324					&EVE
325				),
326				0
327			);
328
329			// when mapped the full account id is returned
330			<Test as Config>::AddressMapper::map(&EVE).unwrap();
331			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
332			assert_eq!(EVE, <Test as Config>::AddressMapper::to_account_id(&EVE_ADDR));
333			assert!(
334				<Test as Config>::Currency::balance_on_hold(
335					&HoldReason::AddressMapping.into(),
336					&EVE
337				) > 0
338			);
339		});
340	}
341
342	#[test]
343	fn map_fallback_account_fails() {
344		ExtBuilder::default().build().execute_with(|| {
345			assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
346			// alice is an e suffixed account and hence cannot be mapped
347			assert_err!(
348				<Test as Config>::AddressMapper::map(&ALICE),
349				<Error<Test>>::AccountAlreadyMapped,
350			);
351			assert_eq!(
352				<Test as Config>::Currency::balance_on_hold(
353					&HoldReason::AddressMapping.into(),
354					&ALICE
355				),
356				0
357			);
358		});
359	}
360
361	#[test]
362	fn double_map_fails() {
363		ExtBuilder::default().build().execute_with(|| {
364			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
365			<Test as Config>::Currency::set_balance(&EVE, 1_000_000);
366			<Test as Config>::AddressMapper::map(&EVE).unwrap();
367			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
368			let deposit = <Test as Config>::Currency::balance_on_hold(
369				&HoldReason::AddressMapping.into(),
370				&EVE,
371			);
372			assert_err!(
373				<Test as Config>::AddressMapper::map(&EVE),
374				<Error<Test>>::AccountAlreadyMapped,
375			);
376			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
377			assert_eq!(
378				<Test as Config>::Currency::balance_on_hold(
379					&HoldReason::AddressMapping.into(),
380					&EVE
381				),
382				deposit
383			);
384		});
385	}
386
387	#[test]
388	fn unmap_works() {
389		ExtBuilder::default().build().execute_with(|| {
390			<Test as Config>::Currency::set_balance(&EVE, 1_000_000);
391			<Test as Config>::AddressMapper::map(&EVE).unwrap();
392			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
393			assert!(
394				<Test as Config>::Currency::balance_on_hold(
395					&HoldReason::AddressMapping.into(),
396					&EVE
397				) > 0
398			);
399
400			<Test as Config>::AddressMapper::unmap(&EVE).unwrap();
401			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
402			assert_eq!(
403				<Test as Config>::Currency::balance_on_hold(
404					&HoldReason::AddressMapping.into(),
405					&EVE
406				),
407				0
408			);
409
410			// another unmap is a noop
411			<Test as Config>::AddressMapper::unmap(&EVE).unwrap();
412			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
413			assert_eq!(
414				<Test as Config>::Currency::balance_on_hold(
415					&HoldReason::AddressMapping.into(),
416					&EVE
417				),
418				0
419			);
420		});
421	}
422}