1
// Copyright 2019-2022 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_collective instances.
18

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

            
21
use account::SYSTEM_ACCOUNT_SIZE;
22
use core::marker::PhantomData;
23
use fp_evm::Log;
24
use frame_support::{
25
	dispatch::{GetDispatchInfo, Pays, PostDispatchInfo},
26
	sp_runtime::traits::Hash,
27
	traits::ConstU32,
28
	weights::Weight,
29
};
30
use pallet_evm::AddressMapping;
31
use parity_scale_codec::DecodeLimit as _;
32
use precompile_utils::prelude::*;
33
use sp_core::{Decode, Get, H160, H256};
34
use sp_runtime::traits::Dispatchable;
35
use sp_std::{boxed::Box, vec::Vec};
36

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

            
42
/// Solidity selector of the Executed log.
43
pub const SELECTOR_LOG_EXECUTED: [u8; 32] = keccak256!("Executed(bytes32)");
44

            
45
/// Solidity selector of the Proposed log.
46
pub const SELECTOR_LOG_PROPOSED: [u8; 32] = keccak256!("Proposed(address,uint32,bytes32,uint32)");
47

            
48
/// Solidity selector of the Voted log.
49
pub const SELECTOR_LOG_VOTED: [u8; 32] = keccak256!("Voted(address,bytes32,bool)");
50

            
51
/// Solidity selector of the Closed log.
52
pub const SELECTOR_LOG_CLOSED: [u8; 32] = keccak256!("Closed(bytes32)");
53

            
54
10
pub fn log_executed(address: impl Into<H160>, hash: H256) -> Log {
55
10
	log2(address.into(), SELECTOR_LOG_EXECUTED, hash, Vec::new())
56
10
}
57

            
58
14
pub fn log_proposed(
59
14
	address: impl Into<H160>,
60
14
	who: impl Into<H160>,
61
14
	index: u32,
62
14
	hash: H256,
63
14
	threshold: u32,
64
14
) -> Log {
65
14
	log4(
66
14
		address.into(),
67
14
		SELECTOR_LOG_PROPOSED,
68
14
		who.into(),
69
14
		H256::from_slice(&solidity::encode_arguments(index)),
70
14
		hash,
71
14
		solidity::encode_arguments(threshold),
72
14
	)
73
14
}
74

            
75
12
pub fn log_voted(address: impl Into<H160>, who: impl Into<H160>, hash: H256, voted: bool) -> Log {
76
12
	log3(
77
12
		address.into(),
78
12
		SELECTOR_LOG_VOTED,
79
12
		who.into(),
80
12
		hash,
81
12
		solidity::encode_arguments(voted),
82
12
	)
83
12
}
84

            
85
2
pub fn log_closed(address: impl Into<H160>, hash: H256) -> Log {
86
2
	log2(address.into(), SELECTOR_LOG_CLOSED, hash, Vec::new())
87
2
}
88

            
89
type GetProposalLimit = ConstU32<{ 2u32.pow(16) }>;
90
type DecodeLimit = ConstU32<8>;
91

            
92
pub struct CollectivePrecompile<Runtime, Instance: 'static>(PhantomData<(Runtime, Instance)>);
93

            
94
271
#[precompile_utils::precompile]
95
impl<Runtime, Instance> CollectivePrecompile<Runtime, Instance>
96
where
97
	Instance: 'static,
98
	Runtime: pallet_collective::Config<Instance> + pallet_evm::Config,
99
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo + Decode,
100
	Runtime::RuntimeCall: From<pallet_collective::Call<Runtime, Instance>>,
101
	<Runtime as pallet_collective::Config<Instance>>::Proposal: From<Runtime::RuntimeCall>,
102
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
103
	Runtime::AccountId: Into<H160>,
104
	H256: From<<Runtime as frame_system::Config>::Hash>
105
		+ Into<<Runtime as frame_system::Config>::Hash>,
106
{
107
	#[precompile::public("execute(bytes)")]
108
3
	fn execute(
109
3
		handle: &mut impl PrecompileHandle,
110
3
		proposal: BoundedBytes<GetProposalLimit>,
111
3
	) -> EvmResult {
112
3
		let proposal: Vec<_> = proposal.into();
113
3
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
114
3

            
115
3
		let log = log_executed(handle.context().address, proposal_hash);
116
3
		handle.record_log_costs(&[&log])?;
117

            
118
3
		let proposal_length: u32 = proposal.len().try_into().map_err(|_| {
119
			RevertReason::value_is_too_large("uint32")
120
				.in_field("length")
121
				.in_field("proposal")
122
3
		})?;
123

            
124
2
		let proposal =
125
3
			Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*proposal)
126
3
				.map_err(|_| {
127
1
					RevertReason::custom("Failed to decode proposal").in_field("proposal")
128
3
				})?
129
2
				.into();
130
2
		let proposal = Box::new(proposal);
131
2

            
132
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
133
2
		RuntimeHelper::<Runtime>::try_dispatch(
134
2
			handle,
135
2
			Some(origin).into(),
136
2
			pallet_collective::Call::<Runtime, Instance>::execute {
137
2
				proposal,
138
2
				length_bound: proposal_length,
139
2
			},
140
2
			SYSTEM_ACCOUNT_SIZE,
141
2
		)?;
142

            
143
		log.record(handle)?;
144

            
145
		Ok(())
146
3
	}
147

            
148
	#[precompile::public("propose(uint32,bytes)")]
149
11
	fn propose(
150
11
		handle: &mut impl PrecompileHandle,
151
11
		threshold: u32,
152
11
		proposal: BoundedBytes<GetProposalLimit>,
153
11
	) -> EvmResult<u32> {
154
11
		// ProposalCount
155
11
		handle.record_db_read::<Runtime>(4)?;
156

            
157
11
		let proposal: Vec<_> = proposal.into();
158
11
		let proposal_length: u32 = proposal.len().try_into().map_err(|_| {
159
			RevertReason::value_is_too_large("uint32")
160
				.in_field("length")
161
				.in_field("proposal")
162
11
		})?;
163

            
164
11
		let proposal_index = pallet_collective::ProposalCount::<Runtime, Instance>::get();
165
11
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
166

            
167
		// In pallet_collective a threshold < 2 means the proposal has been
168
		// executed directly.
169
11
		let log = if threshold < 2 {
170
4
			log_executed(handle.context().address, proposal_hash)
171
		} else {
172
7
			log_proposed(
173
7
				handle.context().address,
174
7
				handle.context().caller,
175
7
				proposal_index,
176
7
				proposal_hash,
177
7
				threshold,
178
7
			)
179
		};
180

            
181
11
		handle.record_log_costs(&[&log])?;
182

            
183
10
		let proposal =
184
11
			Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*proposal)
185
11
				.map_err(|_| {
186
1
					RevertReason::custom("Failed to decode proposal").in_field("proposal")
187
11
				})?
188
10
				.into();
189
10
		let proposal = Box::new(proposal);
190
10

            
191
10
		{
192
10
			let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
193
10
			RuntimeHelper::<Runtime>::try_dispatch(
194
10
				handle,
195
10
				Some(origin).into(),
196
10
				pallet_collective::Call::<Runtime, Instance>::propose {
197
10
					threshold,
198
10
					proposal,
199
10
					length_bound: proposal_length,
200
10
				},
201
10
				SYSTEM_ACCOUNT_SIZE,
202
10
			)?;
203
		}
204

            
205
8
		log.record(handle)?;
206

            
207
8
		Ok(proposal_index)
208
11
	}
209

            
210
	#[precompile::public("vote(bytes32,uint32,bool)")]
211
7
	fn vote(
212
7
		handle: &mut impl PrecompileHandle,
213
7
		proposal_hash: H256,
214
7
		proposal_index: u32,
215
7
		approve: bool,
216
7
	) -> EvmResult {
217
7
		// TODO: Since we cannot access ayes/nays of a proposal we cannot
218
7
		// include it in the EVM events to mirror Substrate events.
219
7
		let log = log_voted(
220
7
			handle.context().address,
221
7
			handle.context().caller,
222
7
			proposal_hash,
223
7
			approve,
224
7
		);
225
7
		handle.record_log_costs(&[&log])?;
226

            
227
7
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
228
7
		RuntimeHelper::<Runtime>::try_dispatch(
229
7
			handle,
230
7
			Some(origin).into(),
231
7
			pallet_collective::Call::<Runtime, Instance>::vote {
232
7
				proposal: proposal_hash.into(),
233
7
				index: proposal_index,
234
7
				approve,
235
7
			},
236
7
			SYSTEM_ACCOUNT_SIZE,
237
7
		)?;
238

            
239
5
		log.record(handle)?;
240

            
241
5
		Ok(())
242
7
	}
243

            
244
	#[precompile::public("close(bytes32,uint32,uint64,uint32)")]
245
4
	fn close(
246
4
		handle: &mut impl PrecompileHandle,
247
4
		proposal_hash: H256,
248
4
		proposal_index: u32,
249
4
		proposal_weight_bound: u64,
250
4
		length_bound: u32,
251
4
	) -> EvmResult<bool> {
252
4
		// Because the actual log cannot be built before dispatch, we manually
253
4
		// record it first (`executed` and `closed` have the same cost).
254
4
		handle.record_log_costs_manual(2, 0)?;
255

            
256
4
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
257
4
		let post_dispatch_info = RuntimeHelper::<Runtime>::try_dispatch(
258
4
			handle,
259
4
			Some(origin).into(),
260
4
			pallet_collective::Call::<Runtime, Instance>::close {
261
4
				proposal_hash: proposal_hash.into(),
262
4
				index: proposal_index,
263
4
				proposal_weight_bound: Weight::from_parts(
264
4
					proposal_weight_bound,
265
4
					xcm_primitives::DEFAULT_PROOF_SIZE,
266
4
				),
267
4
				length_bound,
268
4
			},
269
4
			SYSTEM_ACCOUNT_SIZE,
270
4
		)?;
271

            
272
		// We can know if the proposal was executed or not based on the `pays_fee` in
273
		// `PostDispatchInfo`.
274
2
		let (executed, log) = match post_dispatch_info.pays_fee {
275
1
			Pays::Yes => (true, log_executed(handle.context().address, proposal_hash)),
276
1
			Pays::No => (false, log_closed(handle.context().address, proposal_hash)),
277
		};
278
2
		log.record(handle)?;
279

            
280
2
		Ok(executed)
281
4
	}
282

            
283
	#[precompile::public("proposalHash(bytes)")]
284
	#[precompile::view]
285
	fn proposal_hash(
286
		_handle: &mut impl PrecompileHandle,
287
		proposal: BoundedBytes<GetProposalLimit>,
288
	) -> EvmResult<H256> {
289
		let proposal: Vec<_> = proposal.into();
290
		let hash = hash::<Runtime>(&proposal);
291

            
292
		Ok(hash)
293
	}
294

            
295
	#[precompile::public("proposals()")]
296
	#[precompile::view]
297
1
	fn proposals(handle: &mut impl PrecompileHandle) -> EvmResult<Vec<H256>> {
298
1
		// Proposals: BoundedVec(32 * MaxProposals)
299
1
		handle.record_db_read::<Runtime>(
300
1
			32 * (<Runtime as pallet_collective::Config<Instance>>::MaxProposals::get() as usize),
301
1
		)?;
302

            
303
1
		let proposals = pallet_collective::Proposals::<Runtime, Instance>::get();
304
1
		let proposals: Vec<_> = proposals.into_iter().map(|hash| hash.into()).collect();
305
1

            
306
1
		Ok(proposals)
307
1
	}
308

            
309
	#[precompile::public("members()")]
310
	#[precompile::view]
311
2
	fn members(handle: &mut impl PrecompileHandle) -> EvmResult<Vec<Address>> {
312
2
		// Members: Vec(20 * MaxMembers)
313
2
		handle.record_db_read::<Runtime>(
314
2
			20 * (<Runtime as pallet_collective::Config<Instance>>::MaxProposals::get() as usize),
315
2
		)?;
316

            
317
2
		let members = pallet_collective::Members::<Runtime, Instance>::get();
318
4
		let members: Vec<_> = members.into_iter().map(|id| Address(id.into())).collect();
319
2

            
320
2
		Ok(members)
321
2
	}
322

            
323
	#[precompile::public("isMember(address)")]
324
	#[precompile::view]
325
2
	fn is_member(handle: &mut impl PrecompileHandle, account: Address) -> EvmResult<bool> {
326
2
		// Members: Vec(20 * MaxMembers)
327
2
		handle.record_db_read::<Runtime>(
328
2
			20 * (<Runtime as pallet_collective::Config<Instance>>::MaxProposals::get() as usize),
329
2
		)?;
330

            
331
2
		let account = Runtime::AddressMapping::into_account_id(account.into());
332
2

            
333
2
		let is_member = pallet_collective::Pallet::<Runtime, Instance>::is_member(&account);
334
2

            
335
2
		Ok(is_member)
336
2
	}
337

            
338
	#[precompile::public("prime()")]
339
	#[precompile::view]
340
3
	fn prime(handle: &mut impl PrecompileHandle) -> EvmResult<Address> {
341
3
		// Prime
342
3
		handle.record_db_read::<Runtime>(20)?;
343

            
344
3
		let prime = pallet_collective::Prime::<Runtime, Instance>::get()
345
3
			.map(|prime| prime.into())
346
3
			.unwrap_or(H160::zero());
347
3

            
348
3
		Ok(Address(prime))
349
3
	}
350
}
351

            
352
22
pub fn hash<Runtime>(data: &[u8]) -> H256
353
22
where
354
22
	Runtime: frame_system::Config,
355
22
	H256: From<<Runtime as frame_system::Config>::Hash>,
356
22
{
357
22
	<Runtime as frame_system::Config>::Hashing::hash(data).into()
358
22
}