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.
102#[allow(dead_code)]
103pub struct H160Mapper<T>(PhantomData<T>);
104
105/// An account mapper that can be used for testing u64 account ids.
106pub struct TestAccountMapper<T>(PhantomData<T>);
107
108impl<T> AddressMapper<T> for AccountId32Mapper<T>
109where
110	T: Config<AccountId = AccountId32>,
111{
112	fn to_address(account_id: &AccountId32) -> H160 {
113		let account_bytes: &[u8; 32] = account_id.as_ref();
114		if is_eth_derived(account_id) {
115			// this was originally an eth address
116			// we just strip the 0xEE suffix to get the original address
117			H160::from_slice(&account_bytes[..20])
118		} else {
119			// this is an (ed|sr)25510 derived address
120			// avoid truncating the public key by hashing it first
121			let account_hash = keccak_256(account_bytes);
122			H160::from_slice(&account_hash[12..])
123		}
124	}
125
126	fn to_account_id(address: &H160) -> AccountId32 {
127		<OriginalAccount<T>>::get(address).unwrap_or_else(|| Self::to_fallback_account_id(address))
128	}
129
130	fn to_fallback_account_id(address: &H160) -> AccountId32 {
131		let mut account_id = AccountId32::new([0xEE; 32]);
132		let account_bytes: &mut [u8; 32] = account_id.as_mut();
133		account_bytes[..20].copy_from_slice(address.as_bytes());
134		account_id
135	}
136
137	fn map(account_id: &T::AccountId) -> DispatchResult {
138		ensure!(!Self::is_mapped(account_id), <Error<T>>::AccountAlreadyMapped);
139
140		// each mapping entry stores the address (20 bytes) and the account id (32 bytes)
141		let deposit = T::DepositPerByte::get()
142			.saturating_mul(52u32.into())
143			.saturating_add(T::DepositPerItem::get());
144		T::Currency::hold(&HoldReason::AddressMapping.into(), account_id, deposit)?;
145
146		<OriginalAccount<T>>::insert(Self::to_address(account_id), account_id);
147		Ok(())
148	}
149
150	fn map_no_deposit(account_id: &T::AccountId) -> DispatchResult {
151		ensure!(!Self::is_mapped(account_id), <Error<T>>::AccountAlreadyMapped);
152		<OriginalAccount<T>>::insert(Self::to_address(account_id), account_id);
153		Ok(())
154	}
155
156	fn unmap(account_id: &T::AccountId) -> DispatchResult {
157		// will do nothing if address is not mapped so no check required
158		<OriginalAccount<T>>::remove(Self::to_address(account_id));
159		T::Currency::release_all(
160			&HoldReason::AddressMapping.into(),
161			account_id,
162			Precision::BestEffort,
163		)?;
164		Ok(())
165	}
166
167	fn is_mapped(account_id: &T::AccountId) -> bool {
168		is_eth_derived(account_id) ||
169			<OriginalAccount<T>>::contains_key(Self::to_address(account_id))
170	}
171}
172
173impl<T> AddressMapper<T> for TestAccountMapper<T>
174where
175	T: Config<AccountId = u64>,
176{
177	fn to_address(account_id: &T::AccountId) -> H160 {
178		let mut bytes = [0u8; 20];
179		bytes[12..].copy_from_slice(&account_id.to_be_bytes());
180		H160::from(bytes)
181	}
182
183	fn to_account_id(address: &H160) -> T::AccountId {
184		Self::to_fallback_account_id(address)
185	}
186
187	fn to_fallback_account_id(address: &H160) -> T::AccountId {
188		u64::from_be_bytes(address.as_ref()[12..].try_into().unwrap())
189	}
190
191	fn map(_account_id: &T::AccountId) -> DispatchResult {
192		Ok(())
193	}
194
195	fn unmap(_account_id: &T::AccountId) -> DispatchResult {
196		Ok(())
197	}
198
199	fn is_mapped(_account_id: &T::AccountId) -> bool {
200		true
201	}
202}
203
204/// Returns true if the passed account id is controlled by an eth key.
205///
206/// This is a stateless check that just compares the last 12 bytes. Please note that it is
207/// theoretically possible to create an ed25519 keypair that passed this filter. However,
208/// this can't be used for an attack. It also won't happen by accident since everybody is using
209/// sr25519 where this is not a valid public key.
210pub fn is_eth_derived(account_id: &AccountId32) -> bool {
211	let account_bytes: &[u8; 32] = account_id.as_ref();
212	&account_bytes[20..] == &[0xEE; 12]
213}
214
215impl<T> AddressMapper<T> for H160Mapper<T>
216where
217	T: Config,
218	crate::AccountIdOf<T>: AsRef<[u8; 20]> + From<H160>,
219{
220	fn to_address(account_id: &T::AccountId) -> H160 {
221		H160::from_slice(account_id.as_ref())
222	}
223
224	fn to_account_id(address: &H160) -> T::AccountId {
225		Self::to_fallback_account_id(address)
226	}
227
228	fn to_fallback_account_id(address: &H160) -> T::AccountId {
229		(*address).into()
230	}
231
232	fn map(_account_id: &T::AccountId) -> DispatchResult {
233		Ok(())
234	}
235
236	fn unmap(_account_id: &T::AccountId) -> DispatchResult {
237		Ok(())
238	}
239
240	fn is_mapped(_account_id: &T::AccountId) -> bool {
241		true
242	}
243}
244
245/// Determine the address of a contract using CREATE semantics.
246pub fn create1(deployer: &H160, nonce: u64) -> H160 {
247	let mut list = rlp::RlpStream::new_list(2);
248	list.append(&deployer.as_bytes());
249	list.append(&nonce);
250	let hash = keccak_256(&list.out());
251	H160::from_slice(&hash[12..])
252}
253
254/// Determine the address of a contract using the CREATE2 semantics.
255pub fn create2(deployer: &H160, code: &[u8], input_data: &[u8], salt: &[u8; 32]) -> H160 {
256	let init_code_hash = {
257		let init_code: Vec<u8> = code.into_iter().chain(input_data).cloned().collect();
258		keccak_256(init_code.as_ref())
259	};
260	let mut bytes = [0; 85];
261	bytes[0] = 0xff;
262	bytes[1..21].copy_from_slice(deployer.as_bytes());
263	bytes[21..53].copy_from_slice(salt);
264	bytes[53..85].copy_from_slice(&init_code_hash);
265	let hash = keccak_256(&bytes);
266	H160::from_slice(&hash[12..])
267}
268
269#[cfg(test)]
270mod test {
271	use super::*;
272	use crate::{
273		test_utils::*,
274		tests::{ExtBuilder, Test},
275		AddressMapper, Error,
276	};
277	use frame_support::{
278		assert_err,
279		traits::fungible::{InspectHold, Mutate},
280	};
281	use pretty_assertions::assert_eq;
282	use sp_core::{hex2array, H160};
283
284	#[test]
285	fn create1_works() {
286		assert_eq!(
287			create1(&ALICE_ADDR, 1u64),
288			H160(hex2array!("c851da37e4e8d3a20d8d56be2963934b4ad71c3b")),
289		)
290	}
291
292	#[test]
293	fn create2_works() {
294		assert_eq!(
295			create2(
296				&ALICE_ADDR,
297				&hex2array!("600060005560016000"),
298				&hex2array!("55"),
299				&hex2array!("1234567890123456789012345678901234567890123456789012345678901234")
300			),
301			H160(hex2array!("7f31e795e5836a19a8f919ab5a9de9a197ecd2b6")),
302		)
303	}
304
305	#[test]
306	fn fallback_map_works() {
307		assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
308		assert_eq!(
309			ALICE_FALLBACK,
310			<Test as Config>::AddressMapper::to_fallback_account_id(&ALICE_ADDR)
311		);
312		assert_eq!(ALICE_ADDR, <Test as Config>::AddressMapper::to_address(&ALICE_FALLBACK));
313	}
314
315	#[test]
316	fn map_works() {
317		ExtBuilder::default().build().execute_with(|| {
318			<Test as Config>::Currency::set_balance(&EVE, 1_000_000);
319			// before mapping the fallback account is returned
320			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
321			assert_eq!(EVE_FALLBACK, <Test as Config>::AddressMapper::to_account_id(&EVE_ADDR));
322			assert_eq!(
323				<Test as Config>::Currency::balance_on_hold(
324					&HoldReason::AddressMapping.into(),
325					&EVE
326				),
327				0
328			);
329
330			// when mapped the full account id is returned
331			<Test as Config>::AddressMapper::map(&EVE).unwrap();
332			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
333			assert_eq!(EVE, <Test as Config>::AddressMapper::to_account_id(&EVE_ADDR));
334			assert!(
335				<Test as Config>::Currency::balance_on_hold(
336					&HoldReason::AddressMapping.into(),
337					&EVE
338				) > 0
339			);
340		});
341	}
342
343	#[test]
344	fn map_fallback_account_fails() {
345		ExtBuilder::default().build().execute_with(|| {
346			assert!(<Test as Config>::AddressMapper::is_mapped(&ALICE));
347			// alice is an e suffixed account and hence cannot be mapped
348			assert_err!(
349				<Test as Config>::AddressMapper::map(&ALICE),
350				<Error<Test>>::AccountAlreadyMapped,
351			);
352			assert_eq!(
353				<Test as Config>::Currency::balance_on_hold(
354					&HoldReason::AddressMapping.into(),
355					&ALICE
356				),
357				0
358			);
359		});
360	}
361
362	#[test]
363	fn double_map_fails() {
364		ExtBuilder::default().build().execute_with(|| {
365			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
366			<Test as Config>::Currency::set_balance(&EVE, 1_000_000);
367			<Test as Config>::AddressMapper::map(&EVE).unwrap();
368			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
369			let deposit = <Test as Config>::Currency::balance_on_hold(
370				&HoldReason::AddressMapping.into(),
371				&EVE,
372			);
373			assert_err!(
374				<Test as Config>::AddressMapper::map(&EVE),
375				<Error<Test>>::AccountAlreadyMapped,
376			);
377			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
378			assert_eq!(
379				<Test as Config>::Currency::balance_on_hold(
380					&HoldReason::AddressMapping.into(),
381					&EVE
382				),
383				deposit
384			);
385		});
386	}
387
388	#[test]
389	fn unmap_works() {
390		ExtBuilder::default().build().execute_with(|| {
391			<Test as Config>::Currency::set_balance(&EVE, 1_000_000);
392			<Test as Config>::AddressMapper::map(&EVE).unwrap();
393			assert!(<Test as Config>::AddressMapper::is_mapped(&EVE));
394			assert!(
395				<Test as Config>::Currency::balance_on_hold(
396					&HoldReason::AddressMapping.into(),
397					&EVE
398				) > 0
399			);
400
401			<Test as Config>::AddressMapper::unmap(&EVE).unwrap();
402			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
403			assert_eq!(
404				<Test as Config>::Currency::balance_on_hold(
405					&HoldReason::AddressMapping.into(),
406					&EVE
407				),
408				0
409			);
410
411			// another unmap is a noop
412			<Test as Config>::AddressMapper::unmap(&EVE).unwrap();
413			assert!(!<Test as Config>::AddressMapper::is_mapped(&EVE));
414			assert_eq!(
415				<Test as Config>::Currency::balance_on_hold(
416					&HoldReason::AddressMapping.into(),
417					&EVE
418				),
419				0
420			);
421		});
422	}
423}