1#![cfg_attr(not(feature = "std"), no_std)]
20
21use account::SYSTEM_ACCOUNT_SIZE;
22use fp_evm::PrecompileHandle;
23use frame_support::{
24 dispatch::{GetDispatchInfo, PostDispatchInfo},
25 sp_runtime::traits::{Bounded, CheckedSub, Dispatchable, StaticLookup},
26 storage::types::{StorageDoubleMap, StorageMap, ValueQuery},
27 traits::StorageInstance,
28 Blake2_128Concat,
29};
30use pallet_balances::pallet::{
31 Instance1, Instance10, Instance11, Instance12, Instance13, Instance14, Instance15, Instance16,
32 Instance2, Instance3, Instance4, Instance5, Instance6, Instance7, Instance8, Instance9,
33};
34use pallet_evm::AddressMapping;
35use precompile_utils::prelude::*;
36use sp_core::{H160, H256, U256};
37use sp_std::{
38 convert::{TryFrom, TryInto},
39 marker::PhantomData,
40};
41
42mod eip2612;
43use eip2612::Eip2612;
44
45#[cfg(test)]
46mod mock;
47#[cfg(test)]
48mod tests;
49
50pub const SELECTOR_LOG_TRANSFER: [u8; 32] = keccak256!("Transfer(address,address,uint256)");
52
53pub const SELECTOR_LOG_APPROVAL: [u8; 32] = keccak256!("Approval(address,address,uint256)");
55
56pub const SELECTOR_LOG_DEPOSIT: [u8; 32] = keccak256!("Deposit(address,uint256)");
58
59pub const SELECTOR_LOG_WITHDRAWAL: [u8; 32] = keccak256!("Withdrawal(address,uint256)");
61
62pub trait InstanceToPrefix {
65 type ApprovesPrefix: StorageInstance;
67
68 type NoncesPrefix: StorageInstance;
70}
71
72macro_rules! impl_prefix {
74 ($instance:ident, $name:literal) => {
75 paste::paste! {
78 mod [<_impl_prefix_ $instance:snake>] {
79 use super::*;
80
81 pub struct Approves;
82
83 impl StorageInstance for Approves {
84 const STORAGE_PREFIX: &'static str = "Approves";
85
86 fn pallet_prefix() -> &'static str {
87 $name
88 }
89 }
90
91 pub struct Nonces;
92
93 impl StorageInstance for Nonces {
94 const STORAGE_PREFIX: &'static str = "Nonces";
95
96 fn pallet_prefix() -> &'static str {
97 $name
98 }
99 }
100
101 impl InstanceToPrefix for $instance {
102 type ApprovesPrefix = Approves;
103 type NoncesPrefix = Nonces;
104 }
105 }
106 }
107 };
108}
109
110type Instance0 = ();
112
113impl_prefix!(Instance0, "Erc20Instance0Balances");
114impl_prefix!(Instance1, "Erc20Instance1Balances");
115impl_prefix!(Instance2, "Erc20Instance2Balances");
116impl_prefix!(Instance3, "Erc20Instance3Balances");
117impl_prefix!(Instance4, "Erc20Instance4Balances");
118impl_prefix!(Instance5, "Erc20Instance5Balances");
119impl_prefix!(Instance6, "Erc20Instance6Balances");
120impl_prefix!(Instance7, "Erc20Instance7Balances");
121impl_prefix!(Instance8, "Erc20Instance8Balances");
122impl_prefix!(Instance9, "Erc20Instance9Balances");
123impl_prefix!(Instance10, "Erc20Instance10Balances");
124impl_prefix!(Instance11, "Erc20Instance11Balances");
125impl_prefix!(Instance12, "Erc20Instance12Balances");
126impl_prefix!(Instance13, "Erc20Instance13Balances");
127impl_prefix!(Instance14, "Erc20Instance14Balances");
128impl_prefix!(Instance15, "Erc20Instance15Balances");
129impl_prefix!(Instance16, "Erc20Instance16Balances");
130
131pub type BalanceOf<Runtime, Instance = ()> =
133 <Runtime as pallet_balances::Config<Instance>>::Balance;
134
135pub type ApprovesStorage<Runtime, Instance> = StorageDoubleMap<
139 <Instance as InstanceToPrefix>::ApprovesPrefix,
140 Blake2_128Concat,
141 <Runtime as frame_system::Config>::AccountId,
142 Blake2_128Concat,
143 <Runtime as frame_system::Config>::AccountId,
144 BalanceOf<Runtime, Instance>,
145>;
146
147pub type NoncesStorage<Instance> = StorageMap<
149 <Instance as InstanceToPrefix>::NoncesPrefix,
150 Blake2_128Concat,
152 H160,
153 U256,
155 ValueQuery,
156>;
157
158pub trait Erc20Metadata {
160 fn name() -> &'static str;
162
163 fn symbol() -> &'static str;
165
166 fn decimals() -> u8;
168
169 fn is_native_currency() -> bool;
172}
173
174pub struct Erc20BalancesPrecompile<Runtime, Metadata: Erc20Metadata, Instance: 'static = ()>(
178 PhantomData<(Runtime, Metadata, Instance)>,
179);
180
181#[precompile_utils::precompile]
182impl<Runtime, Metadata, Instance> Erc20BalancesPrecompile<Runtime, Metadata, Instance>
183where
184 Runtime: pallet_balances::Config<Instance> + pallet_evm::Config,
185 Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
186 Runtime::RuntimeCall: From<pallet_balances::Call<Runtime, Instance>>,
187 BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256>,
188 Metadata: Erc20Metadata,
189 Instance: InstanceToPrefix + 'static,
190 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
191{
192 #[precompile::public("totalSupply()")]
193 #[precompile::view]
194 fn total_supply(handle: &mut impl PrecompileHandle) -> EvmResult<U256> {
195 handle.record_db_read::<Runtime>(16)?;
197
198 Ok(pallet_balances::Pallet::<Runtime, Instance>::total_issuance().into())
199 }
200
201 #[precompile::public("balanceOf(address)")]
202 #[precompile::view]
203 fn balance_of(handle: &mut impl PrecompileHandle, owner: Address) -> EvmResult<U256> {
204 handle.record_db_read::<Runtime>(116)?;
207
208 let owner: H160 = owner.into();
209 let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
210
211 Ok(pallet_balances::Pallet::<Runtime, Instance>::usable_balance(&owner).into())
212 }
213
214 #[precompile::public("allowance(address,address)")]
215 #[precompile::view]
216 fn allowance(
217 handle: &mut impl PrecompileHandle,
218 owner: Address,
219 spender: Address,
220 ) -> EvmResult<U256> {
221 handle.record_db_read::<Runtime>(88)?;
224
225 let owner: H160 = owner.into();
226 let spender: H160 = spender.into();
227
228 let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
229 let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
230
231 Ok(ApprovesStorage::<Runtime, Instance>::get(owner, spender)
232 .unwrap_or_default()
233 .into())
234 }
235
236 #[precompile::public("approve(address,uint256)")]
237 fn approve(
238 handle: &mut impl PrecompileHandle,
239 spender: Address,
240 value: U256,
241 ) -> EvmResult<bool> {
242 handle.record_cost(RuntimeHelper::<Runtime>::db_write_gas_cost())?;
243 handle.record_log_costs_manual(3, 32)?;
244
245 let spender: H160 = spender.into();
246
247 {
249 let caller: Runtime::AccountId =
250 Runtime::AddressMapping::into_account_id(handle.context().caller);
251 let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
252 let value = Self::u256_to_amount(value).unwrap_or_else(|_| Bounded::max_value());
254
255 ApprovesStorage::<Runtime, Instance>::insert(caller, spender, value);
256 }
257
258 log3(
259 handle.context().address,
260 SELECTOR_LOG_APPROVAL,
261 handle.context().caller,
262 spender,
263 solidity::encode_event_data(value),
264 )
265 .record(handle)?;
266
267 Ok(true)
269 }
270
271 #[precompile::public("transfer(address,uint256)")]
272 fn transfer(handle: &mut impl PrecompileHandle, to: Address, value: U256) -> EvmResult<bool> {
273 handle.record_log_costs_manual(3, 32)?;
274
275 let to: H160 = to.into();
276
277 {
279 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
280 let to = Runtime::AddressMapping::into_account_id(to);
281 let value = Self::u256_to_amount(value).in_field("value")?;
282
283 RuntimeHelper::<Runtime>::try_dispatch(
285 handle,
286 frame_system::RawOrigin::Signed(origin).into(),
287 pallet_balances::Call::<Runtime, Instance>::transfer_allow_death {
288 dest: Runtime::Lookup::unlookup(to),
289 value,
290 },
291 SYSTEM_ACCOUNT_SIZE,
292 )?;
293 }
294
295 log3(
296 handle.context().address,
297 SELECTOR_LOG_TRANSFER,
298 handle.context().caller,
299 to,
300 solidity::encode_event_data(value),
301 )
302 .record(handle)?;
303
304 Ok(true)
305 }
306
307 #[precompile::public("transferFrom(address,address,uint256)")]
308 fn transfer_from(
309 handle: &mut impl PrecompileHandle,
310 from: Address,
311 to: Address,
312 value: U256,
313 ) -> EvmResult<bool> {
314 handle.record_db_read::<Runtime>(88)?;
317 handle.record_cost(RuntimeHelper::<Runtime>::db_write_gas_cost())?;
318 handle.record_log_costs_manual(3, 32)?;
319
320 let from: H160 = from.into();
321 let to: H160 = to.into();
322
323 {
324 let caller: Runtime::AccountId =
325 Runtime::AddressMapping::into_account_id(handle.context().caller);
326 let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from);
327 let to: Runtime::AccountId = Runtime::AddressMapping::into_account_id(to);
328 let value = Self::u256_to_amount(value).in_field("value")?;
329
330 if caller != from {
332 ApprovesStorage::<Runtime, Instance>::mutate(from.clone(), caller, |entry| {
333 let allowed = entry.ok_or(revert("spender not allowed"))?;
335
336 let allowed = allowed
338 .checked_sub(&value)
339 .ok_or_else(|| revert("trying to spend more than allowed"))?;
340
341 *entry = Some(allowed);
343
344 EvmResult::Ok(())
345 })?;
346 }
347
348 RuntimeHelper::<Runtime>::try_dispatch(
351 handle,
352 frame_system::RawOrigin::Signed(from).into(),
353 pallet_balances::Call::<Runtime, Instance>::transfer_allow_death {
354 dest: Runtime::Lookup::unlookup(to),
355 value,
356 },
357 SYSTEM_ACCOUNT_SIZE,
358 )?;
359 }
360
361 log3(
362 handle.context().address,
363 SELECTOR_LOG_TRANSFER,
364 from,
365 to,
366 solidity::encode_event_data(value),
367 )
368 .record(handle)?;
369
370 Ok(true)
371 }
372
373 #[precompile::public("name()")]
374 #[precompile::view]
375 fn name(_handle: &mut impl PrecompileHandle) -> EvmResult<UnboundedBytes> {
376 Ok(Metadata::name().into())
377 }
378
379 #[precompile::public("symbol()")]
380 #[precompile::view]
381 fn symbol(_handle: &mut impl PrecompileHandle) -> EvmResult<UnboundedBytes> {
382 Ok(Metadata::symbol().into())
383 }
384
385 #[precompile::public("decimals()")]
386 #[precompile::view]
387 fn decimals(_handle: &mut impl PrecompileHandle) -> EvmResult<u8> {
388 Ok(Metadata::decimals())
389 }
390
391 #[precompile::public("deposit()")]
392 #[precompile::fallback]
393 #[precompile::payable]
394 fn deposit(handle: &mut impl PrecompileHandle) -> EvmResult {
395 if !Metadata::is_native_currency() {
397 return Err(RevertReason::UnknownSelector.into());
398 }
399
400 let caller: Runtime::AccountId =
401 Runtime::AddressMapping::into_account_id(handle.context().caller);
402 let precompile = Runtime::AddressMapping::into_account_id(handle.context().address);
403 let amount = Self::u256_to_amount(handle.context().apparent_value)?;
404
405 if amount.into() == U256::from(0u32) {
406 return Err(revert("deposited amount must be non-zero"));
407 }
408
409 handle.record_log_costs_manual(2, 32)?;
410
411 RuntimeHelper::<Runtime>::try_dispatch(
413 handle,
414 frame_system::RawOrigin::Signed(precompile).into(),
415 pallet_balances::Call::<Runtime, Instance>::transfer_allow_death {
416 dest: Runtime::Lookup::unlookup(caller),
417 value: amount,
418 },
419 SYSTEM_ACCOUNT_SIZE,
420 )?;
421
422 log2(
423 handle.context().address,
424 SELECTOR_LOG_DEPOSIT,
425 handle.context().caller,
426 solidity::encode_event_data(handle.context().apparent_value),
427 )
428 .record(handle)?;
429
430 Ok(())
431 }
432
433 #[precompile::public("withdraw(uint256)")]
434 fn withdraw(handle: &mut impl PrecompileHandle, value: U256) -> EvmResult {
435 if !Metadata::is_native_currency() {
437 return Err(RevertReason::UnknownSelector.into());
438 }
439
440 handle.record_log_costs_manual(2, 32)?;
441
442 let account_amount: U256 = {
443 let owner: Runtime::AccountId =
444 Runtime::AddressMapping::into_account_id(handle.context().caller);
445 pallet_balances::Pallet::<Runtime, Instance>::usable_balance(&owner).into()
446 };
447
448 if value > account_amount {
449 return Err(revert("Trying to withdraw more than owned"));
450 }
451
452 log2(
453 handle.context().address,
454 SELECTOR_LOG_WITHDRAWAL,
455 handle.context().caller,
456 solidity::encode_event_data(value),
457 )
458 .record(handle)?;
459
460 Ok(())
461 }
462
463 #[precompile::public("permit(address,address,uint256,uint256,uint8,bytes32,bytes32)")]
464 #[allow(clippy::too_many_arguments)]
465 fn eip2612_permit(
466 handle: &mut impl PrecompileHandle,
467 owner: Address,
468 spender: Address,
469 value: U256,
470 deadline: U256,
471 v: u8,
472 r: H256,
473 s: H256,
474 ) -> EvmResult {
475 <Eip2612<Runtime, Metadata, Instance>>::permit(
476 handle, owner, spender, value, deadline, v, r, s,
477 )
478 }
479
480 #[precompile::public("nonces(address)")]
481 #[precompile::view]
482 fn eip2612_nonces(handle: &mut impl PrecompileHandle, owner: Address) -> EvmResult<U256> {
483 <Eip2612<Runtime, Metadata, Instance>>::nonces(handle, owner)
484 }
485
486 #[precompile::public("DOMAIN_SEPARATOR()")]
487 #[precompile::view]
488 fn eip2612_domain_separator(handle: &mut impl PrecompileHandle) -> EvmResult<H256> {
489 <Eip2612<Runtime, Metadata, Instance>>::domain_separator(handle)
490 }
491
492 fn u256_to_amount(value: U256) -> MayRevert<BalanceOf<Runtime, Instance>> {
493 value
494 .try_into()
495 .map_err(|_| RevertReason::value_is_too_large("balance type").into())
496 }
497}