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
//! Precompile to interact with pallet author mapping through an evm precompile.
18

            
19
#![cfg_attr(not(feature = "std"), no_std)]
20

            
21
use fp_evm::PrecompileHandle;
22
use frame_support::{
23
	dispatch::{GetDispatchInfo, PostDispatchInfo},
24
	traits::Get,
25
};
26
use nimbus_primitives::NimbusId;
27
use pallet_author_mapping::Call as AuthorMappingCall;
28
use pallet_evm::AddressMapping;
29
use parity_scale_codec::Encode;
30
use precompile_utils::prelude::*;
31
use sp_core::crypto::UncheckedFrom;
32
use sp_core::{H160, H256};
33
use sp_runtime::traits::Dispatchable;
34
use sp_std::marker::PhantomData;
35

            
36
#[cfg(test)]
37
mod mock;
38
#[cfg(test)]
39
mod tests;
40

            
41
/// A precompile to wrap the functionality from pallet author mapping.
42
pub struct AuthorMappingPrecompile<Runtime>(PhantomData<Runtime>);
43

            
44
/// Bound for the keys size.
45
/// Pallet will check that the size exactly matches, but we want to bound the parser to
46
/// not accept larger bytes.
47
pub struct GetKeysSize<R>(PhantomData<R>);
48

            
49
impl<R: pallet_author_mapping::Config> Get<u32> for GetKeysSize<R> {
50
3
	fn get() -> u32 {
51
3
		pallet_author_mapping::pallet::keys_size::<R>()
52
3
			.try_into()
53
3
			.expect("keys size to fit in u32")
54
3
	}
55
}
56

            
57
213
#[precompile_utils::precompile]
58
#[precompile::test_concrete_types(mock::Runtime)]
59
impl<Runtime> AuthorMappingPrecompile<Runtime>
60
where
61
	Runtime: pallet_author_mapping::Config + pallet_evm::Config + frame_system::Config,
62
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
63
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
64
	Runtime::RuntimeCall: From<AuthorMappingCall<Runtime>>,
65
	Runtime::Hash: From<H256>,
66
	Runtime::AccountId: Into<H160>,
67
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
68
{
69
	// The dispatchable wrappers are next. They dispatch a Substrate inner Call.
70
	#[precompile::public("addAssociation(bytes32)")]
71
	#[precompile::public("add_association(bytes32)")]
72
2
	fn add_association(handle: &mut impl PrecompileHandle, nimbus_id: H256) -> EvmResult {
73
2
		let nimbus_id = sp_core::sr25519::Public::unchecked_from(nimbus_id).into();
74
2

            
75
2
		log::trace!(
76
			target: "author-mapping-precompile",
77
			"Associating author id {:?}", nimbus_id
78
		);
79

            
80
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
81
2
		let call = AuthorMappingCall::<Runtime>::add_association { nimbus_id };
82
2

            
83
2
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
84

            
85
2
		Ok(())
86
2
	}
87

            
88
	#[precompile::public("updateAssociation(bytes32,bytes32)")]
89
	#[precompile::public("update_association(bytes32,bytes32)")]
90
2
	fn update_association(
91
2
		handle: &mut impl PrecompileHandle,
92
2
		old_nimbus_id: H256,
93
2
		new_nimbus_id: H256,
94
2
	) -> EvmResult {
95
2
		let old_nimbus_id = sp_core::sr25519::Public::unchecked_from(old_nimbus_id).into();
96
2
		let new_nimbus_id = sp_core::sr25519::Public::unchecked_from(new_nimbus_id).into();
97
2

            
98
2
		log::trace!(
99
			target: "author-mapping-precompile",
100
			"Updating author id {:?} for {:?}", old_nimbus_id, new_nimbus_id
101
		);
102

            
103
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
104
2
		let call = AuthorMappingCall::<Runtime>::update_association {
105
2
			old_nimbus_id,
106
2
			new_nimbus_id,
107
2
		};
108
2

            
109
2
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
110

            
111
2
		Ok(())
112
2
	}
113

            
114
	#[precompile::public("clearAssociation(bytes32)")]
115
	#[precompile::public("clear_association(bytes32)")]
116
2
	fn clear_association(handle: &mut impl PrecompileHandle, nimbus_id: H256) -> EvmResult {
117
2
		let nimbus_id = sp_core::sr25519::Public::unchecked_from(nimbus_id).into();
118
2

            
119
2
		log::trace!(
120
			target: "author-mapping-precompile",
121
			"Clearing author id {:?}", nimbus_id
122
		);
123

            
124
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
125
2
		let call = AuthorMappingCall::<Runtime>::clear_association { nimbus_id };
126
2

            
127
2
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
128

            
129
2
		Ok(())
130
2
	}
131

            
132
	#[precompile::public("removeKeys()")]
133
	#[precompile::public("remove_keys()")]
134
1
	fn remove_keys(handle: &mut impl PrecompileHandle) -> EvmResult {
135
1
		log::trace!(
136
			target: "author-mapping-precompile",
137
			"Removing keys"
138
		);
139

            
140
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
141
1
		let call = AuthorMappingCall::<Runtime>::remove_keys {};
142
1

            
143
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
144

            
145
1
		Ok(())
146
1
	}
147

            
148
	#[precompile::public("setKeys(bytes)")]
149
	#[precompile::public("set_keys(bytes)")]
150
3
	fn set_keys(
151
3
		handle: &mut impl PrecompileHandle,
152
3
		keys: BoundedBytes<GetKeysSize<Runtime>>,
153
3
	) -> EvmResult {
154
3
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
155
3
		let call = AuthorMappingCall::<Runtime>::set_keys { keys: keys.into() };
156
3

            
157
3
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
158

            
159
3
		Ok(())
160
3
	}
161

            
162
	#[precompile::public("nimbusIdOf(address)")]
163
	#[precompile::view]
164
2
	fn nimbus_id_of(handle: &mut impl PrecompileHandle, address: Address) -> EvmResult<H256> {
165
2
		// Storage item: NimbusLookup:
166
2
		// Blake2_128(16) + AccountId(20) + NimbusId(32)
167
2
		handle.record_db_read::<Runtime>(68)?;
168
2
		let account = Runtime::AddressMapping::into_account_id(address.0);
169
2

            
170
2
		let nimbus_id = pallet_author_mapping::Pallet::<Runtime>::nimbus_id_of(&account)
171
2
			.map(|x| H256::from(sp_core::sr25519::Public::from(x).0))
172
2
			.unwrap_or(H256::zero());
173
2
		Ok(nimbus_id)
174
2
	}
175

            
176
	#[precompile::public("addressOf(bytes32)")]
177
	#[precompile::view]
178
2
	fn address_of(handle: &mut impl PrecompileHandle, nimbus_id: H256) -> EvmResult<Address> {
179
2
		// Storage item: MappingWithDeposit:
180
2
		// Blake2_128(16) + NimbusId(32) + RegistrationInfo(20 + 16 + VrfId(32))
181
2
		handle.record_db_read::<Runtime>(116)?;
182

            
183
2
		let nimbus_id = sp_core::sr25519::Public::unchecked_from(nimbus_id);
184
2
		let nimbus_id: NimbusId = nimbus_id.into();
185
2

            
186
2
		let address: H160 = pallet_author_mapping::Pallet::<Runtime>::account_id_of(&nimbus_id)
187
2
			.map(|x| x.into())
188
2
			.unwrap_or(H160::zero());
189
2

            
190
2
		Ok(Address(address))
191
2
	}
192

            
193
	#[precompile::public("keysOf(bytes32)")]
194
	#[precompile::view]
195
2
	fn keys_of(handle: &mut impl PrecompileHandle, nimbus_id: H256) -> EvmResult<UnboundedBytes> {
196
2
		// Storage item: MappingWithDeposit:
197
2
		// Blake2_128(16) + NimbusId(32) + RegistrationInfo(20 + 16 + VrfId(32))
198
2
		handle.record_db_read::<Runtime>(116)?;
199

            
200
2
		let nimbus_id = sp_core::sr25519::Public::unchecked_from(nimbus_id);
201
2
		let nimbus_id: NimbusId = nimbus_id.into();
202
2

            
203
2
		let keys = pallet_author_mapping::Pallet::<Runtime>::keys_of(&nimbus_id)
204
2
			.map(|x| x.encode())
205
2
			.unwrap_or_default();
206
2

            
207
2
		Ok(keys.into())
208
2
	}
209
}