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
	BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256>,
45
	Metadata: Erc20Metadata,
46
	Instance: InstanceToPrefix + 'static,
47
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
48
{
49
8
	pub fn compute_domain_separator(address: H160) -> [u8; 32] {
50
8
		let name: H256 = keccak_256(Metadata::name().as_bytes()).into();
51
8
		let version: H256 = keccak256!("1").into();
52
8
		let chain_id: U256 = Runtime::ChainId::get().into();
53

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

            
62
8
		keccak_256(&domain_separator_inner)
63
8
	}
64

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
164
2
		Ok(())
165
5
	}
166

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

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

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

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

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