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
	traits::{Get, Time},
21
};
22
use sp_core::H256;
23
use sp_io::hashing::keccak_256;
24
use sp_runtime::traits::UniqueSaturatedInto;
25
use sp_std::vec::Vec;
26

            
27
/// EIP2612 permit typehash.
28
pub const PERMIT_TYPEHASH: [u8; 32] = keccak256!(
29
	"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
30
);
31

            
32
/// EIP2612 permit domain used to compute an individualized domain separator.
33
const PERMIT_DOMAIN: [u8; 32] = keccak256!(
34
	"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
35
);
36

            
37
pub struct Eip2612<Runtime, Metadata, Instance = ()>(PhantomData<(Runtime, Metadata, Instance)>);
38

            
39
impl<Runtime, Metadata, Instance> Eip2612<Runtime, Metadata, Instance>
40
where
41
	Runtime: pallet_balances::Config<Instance> + pallet_evm::Config,
42
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
43
	Runtime::RuntimeCall: From<pallet_balances::Call<Runtime, Instance>>,
44
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
45
	BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256>,
46
	Metadata: Erc20Metadata,
47
	Instance: InstanceToPrefix + 'static,
48
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
49
{
50
8
	pub fn compute_domain_separator(address: H160) -> [u8; 32] {
51
8
		let name: H256 = keccak_256(Metadata::name().as_bytes()).into();
52
8
		let version: H256 = keccak256!("1").into();
53
8
		let chain_id: U256 = Runtime::ChainId::get().into();
54
8

            
55
8
		let domain_separator_inner = solidity::encode_arguments((
56
8
			H256::from(PERMIT_DOMAIN),
57
8
			name,
58
8
			version,
59
8
			chain_id,
60
8
			Address(address),
61
8
		));
62
8

            
63
8
		keccak_256(&domain_separator_inner)
64
8
	}
65

            
66
7
	pub fn generate_permit(
67
7
		address: H160,
68
7
		owner: H160,
69
7
		spender: H160,
70
7
		value: U256,
71
7
		nonce: U256,
72
7
		deadline: U256,
73
7
	) -> [u8; 32] {
74
7
		let domain_separator = Self::compute_domain_separator(address);
75
7

            
76
7
		let permit_content = solidity::encode_arguments((
77
7
			H256::from(PERMIT_TYPEHASH),
78
7
			Address(owner),
79
7
			Address(spender),
80
7
			value,
81
7
			nonce,
82
7
			deadline,
83
7
		));
84
7
		let permit_content = keccak_256(&permit_content);
85
7

            
86
7
		let mut pre_digest = Vec::with_capacity(2 + 32 + 32);
87
7
		pre_digest.extend_from_slice(b"\x19\x01");
88
7
		pre_digest.extend_from_slice(&domain_separator);
89
7
		pre_digest.extend_from_slice(&permit_content);
90
7
		keccak_256(&pre_digest)
91
7
	}
92

            
93
	// Translated from
94
	// https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol#L81
95
	#[allow(clippy::too_many_arguments)]
96
5
	pub(crate) fn permit(
97
5
		handle: &mut impl PrecompileHandle,
98
5
		owner: Address,
99
5
		spender: Address,
100
5
		value: U256,
101
5
		deadline: U256,
102
5
		v: u8,
103
5
		r: H256,
104
5
		s: H256,
105
5
	) -> EvmResult {
106
5
		// NoncesStorage: Blake2_128(16) + contract(20) + Blake2_128(16) + owner(20) + nonce(32)
107
5
		handle.record_db_read::<Runtime>(104)?;
108

            
109
5
		let owner: H160 = owner.into();
110
5
		let spender: H160 = spender.into();
111
5

            
112
5
		// Blockchain time is in ms while Ethereum use second timestamps.
113
5
		let timestamp: u128 =
114
5
			<Runtime as pallet_evm::Config>::Timestamp::now().unique_saturated_into();
115
5
		let timestamp: U256 = U256::from(timestamp / 1000);
116
5

            
117
5
		ensure!(deadline >= timestamp, revert("Permit expired"));
118

            
119
4
		let nonce = NoncesStorage::<Instance>::get(owner);
120
4

            
121
4
		let permit = Self::generate_permit(
122
4
			handle.context().address,
123
4
			owner,
124
4
			spender,
125
4
			value,
126
4
			nonce,
127
4
			deadline,
128
4
		);
129
4

            
130
4
		let mut sig = [0u8; 65];
131
4
		sig[0..32].copy_from_slice(r.as_bytes());
132
4
		sig[32..64].copy_from_slice(s.as_bytes());
133
4
		sig[64] = v;
134

            
135
4
		let signer = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &permit)
136
4
			.map_err(|_| revert("Invalid permit"))?;
137
3
		let signer = H160::from(H256::from_slice(keccak_256(&signer).as_slice()));
138
3

            
139
3
		ensure!(
140
3
			signer != H160::zero() && signer == owner,
141
1
			revert("Invalid permit")
142
		);
143

            
144
2
		NoncesStorage::<Instance>::insert(owner, nonce + U256::one());
145
2

            
146
2
		{
147
2
			let amount =
148
2
				Erc20BalancesPrecompile::<Runtime, Metadata, Instance>::u256_to_amount(value)
149
2
					.unwrap_or_else(|_| Bounded::max_value());
150
2

            
151
2
			let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
152
2
			let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
153
2
			ApprovesStorage::<Runtime, Instance>::insert(owner, spender, amount);
154
2
		}
155
2

            
156
2
		log3(
157
2
			handle.context().address,
158
2
			SELECTOR_LOG_APPROVAL,
159
2
			owner,
160
2
			spender,
161
2
			solidity::encode_event_data(value),
162
2
		)
163
2
		.record(handle)?;
164

            
165
2
		Ok(())
166
5
	}
167

            
168
8
	pub(crate) fn nonces(handle: &mut impl PrecompileHandle, owner: Address) -> EvmResult<U256> {
169
8
		// NoncesStorage: Blake2_128(16) + contract(20) + Blake2_128(16) + owner(20) + nonce(32)
170
8
		handle.record_db_read::<Runtime>(104)?;
171

            
172
8
		let owner: H160 = owner.into();
173
8

            
174
8
		Ok(NoncesStorage::<Instance>::get(owner))
175
8
	}
176

            
177
1
	pub(crate) fn domain_separator(handle: &mut impl PrecompileHandle) -> EvmResult<H256> {
178
1
		// ChainId
179
1
		handle.record_db_read::<Runtime>(8)?;
180

            
181
1
		Ok(Self::compute_domain_separator(handle.context().address).into())
182
1
	}
183
}