pallet_evm_precompile_collective/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
20
21use account::{AccountId20, SYSTEM_ACCOUNT_SIZE};
22use core::marker::PhantomData;
23use fp_evm::Log;
24use frame_support::{
25 dispatch::{GetDispatchInfo, Pays, PostDispatchInfo},
26 sp_runtime::traits::Hash,
27 traits::ConstU32,
28 weights::Weight,
29};
30use pallet_evm::AddressMapping;
31use parity_scale_codec::{DecodeLimit as _, MaxEncodedLen};
32use precompile_utils::prelude::*;
33use sp_core::{Decode, Get, H160, H256};
34use sp_runtime::traits::Dispatchable;
35use sp_std::{boxed::Box, vec::Vec};
36
37#[cfg(test)]
38mod mock;
39#[cfg(test)]
40mod tests;
41
42pub const SELECTOR_LOG_EXECUTED: [u8; 32] = keccak256!("Executed(bytes32)");
44
45pub const SELECTOR_LOG_PROPOSED: [u8; 32] = keccak256!("Proposed(address,uint32,bytes32,uint32)");
47
48pub const SELECTOR_LOG_VOTED: [u8; 32] = keccak256!("Voted(address,bytes32,bool)");
50
51pub const SELECTOR_LOG_CLOSED: [u8; 32] = keccak256!("Closed(bytes32)");
53
54pub fn log_executed(address: impl Into<H160>, hash: H256) -> Log {
55 log2(address.into(), SELECTOR_LOG_EXECUTED, hash, Vec::new())
56}
57
58pub fn log_proposed(
59 address: impl Into<H160>,
60 who: impl Into<H160>,
61 index: u32,
62 hash: H256,
63 threshold: u32,
64) -> Log {
65 log4(
66 address.into(),
67 SELECTOR_LOG_PROPOSED,
68 who.into(),
69 H256::from_slice(&solidity::encode_arguments(index)),
70 hash,
71 solidity::encode_arguments(threshold),
72 )
73}
74
75pub fn log_voted(address: impl Into<H160>, who: impl Into<H160>, hash: H256, voted: bool) -> Log {
76 log3(
77 address.into(),
78 SELECTOR_LOG_VOTED,
79 who.into(),
80 hash,
81 solidity::encode_arguments(voted),
82 )
83}
84
85pub fn log_closed(address: impl Into<H160>, hash: H256) -> Log {
86 log2(address.into(), SELECTOR_LOG_CLOSED, hash, Vec::new())
87}
88
89type GetProposalLimit = ConstU32<{ 2u32.pow(16) }>;
90type DecodeLimit = ConstU32<8>;
91
92pub struct CollectivePrecompile<Runtime, Instance: 'static>(PhantomData<(Runtime, Instance)>);
93
94#[precompile_utils::precompile]
95impl<Runtime, Instance> CollectivePrecompile<Runtime, Instance>
96where
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::AccountId: Into<H160>,
103 H256: From<<Runtime as frame_system::Config>::Hash>
104 + Into<<Runtime as frame_system::Config>::Hash>,
105 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
106{
107 #[precompile::public("execute(bytes)")]
108 fn execute(
109 handle: &mut impl PrecompileHandle,
110 proposal: BoundedBytes<GetProposalLimit>,
111 ) -> EvmResult {
112 let proposal: Vec<_> = proposal.into();
113 let proposal_hash: H256 = hash::<Runtime>(&proposal);
114
115 let log = log_executed(handle.context().address, proposal_hash);
116 handle.record_log_costs(&[&log])?;
117
118 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 })?;
123
124 let proposal =
125 Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*proposal)
126 .map_err(|_| {
127 RevertReason::custom("Failed to decode proposal").in_field("proposal")
128 })?
129 .into();
130 let proposal = Box::new(proposal);
131
132 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
133 RuntimeHelper::<Runtime>::try_dispatch(
134 handle,
135 frame_system::RawOrigin::Signed(origin).into(),
136 pallet_collective::Call::<Runtime, Instance>::execute {
137 proposal,
138 length_bound: proposal_length,
139 },
140 SYSTEM_ACCOUNT_SIZE,
141 )?;
142
143 log.record(handle)?;
144
145 Ok(())
146 }
147
148 #[precompile::public("propose(uint32,bytes)")]
149 fn propose(
150 handle: &mut impl PrecompileHandle,
151 threshold: u32,
152 proposal: BoundedBytes<GetProposalLimit>,
153 ) -> EvmResult<u32> {
154 handle.record_db_read::<Runtime>(4)?;
156
157 let proposal: Vec<_> = proposal.into();
158 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 })?;
163
164 let proposal_index = pallet_collective::ProposalCount::<Runtime, Instance>::get();
165 let proposal_hash: H256 = hash::<Runtime>(&proposal);
166
167 let log = if threshold < 2 {
170 log_executed(handle.context().address, proposal_hash)
171 } else {
172 log_proposed(
173 handle.context().address,
174 handle.context().caller,
175 proposal_index,
176 proposal_hash,
177 threshold,
178 )
179 };
180
181 handle.record_log_costs(&[&log])?;
182
183 let proposal =
184 Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*proposal)
185 .map_err(|_| {
186 RevertReason::custom("Failed to decode proposal").in_field("proposal")
187 })?
188 .into();
189 let proposal = Box::new(proposal);
190
191 {
192 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
193 RuntimeHelper::<Runtime>::try_dispatch(
194 handle,
195 frame_system::RawOrigin::Signed(origin).into(),
196 pallet_collective::Call::<Runtime, Instance>::propose {
197 threshold,
198 proposal,
199 length_bound: proposal_length,
200 },
201 SYSTEM_ACCOUNT_SIZE,
202 )?;
203 }
204
205 log.record(handle)?;
206
207 Ok(proposal_index)
208 }
209
210 #[precompile::public("vote(bytes32,uint32,bool)")]
211 fn vote(
212 handle: &mut impl PrecompileHandle,
213 proposal_hash: H256,
214 proposal_index: u32,
215 approve: bool,
216 ) -> EvmResult {
217 let log = log_voted(
220 handle.context().address,
221 handle.context().caller,
222 proposal_hash,
223 approve,
224 );
225 handle.record_log_costs(&[&log])?;
226
227 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
228 RuntimeHelper::<Runtime>::try_dispatch(
229 handle,
230 frame_system::RawOrigin::Signed(origin).into(),
231 pallet_collective::Call::<Runtime, Instance>::vote {
232 proposal: proposal_hash.into(),
233 index: proposal_index,
234 approve,
235 },
236 SYSTEM_ACCOUNT_SIZE,
237 )?;
238
239 log.record(handle)?;
240
241 Ok(())
242 }
243
244 #[precompile::public("close(bytes32,uint32,uint64,uint32)")]
245 fn close(
246 handle: &mut impl PrecompileHandle,
247 proposal_hash: H256,
248 proposal_index: u32,
249 proposal_weight_bound: u64,
250 length_bound: u32,
251 ) -> EvmResult<bool> {
252 handle.record_log_costs_manual(2, 0)?;
255
256 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
257 let post_dispatch_info = RuntimeHelper::<Runtime>::try_dispatch(
258 handle,
259 frame_system::RawOrigin::Signed(origin).into(),
260 pallet_collective::Call::<Runtime, Instance>::close {
261 proposal_hash: proposal_hash.into(),
262 index: proposal_index,
263 proposal_weight_bound: Weight::from_parts(
264 proposal_weight_bound,
265 xcm_primitives::DEFAULT_PROOF_SIZE,
266 ),
267 length_bound,
268 },
269 SYSTEM_ACCOUNT_SIZE,
270 )?;
271
272 let (executed, log) = match post_dispatch_info.pays_fee {
275 Pays::Yes => (true, log_executed(handle.context().address, proposal_hash)),
276 Pays::No => (false, log_closed(handle.context().address, proposal_hash)),
277 };
278 log.record(handle)?;
279
280 Ok(executed)
281 }
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 fn proposals(handle: &mut impl PrecompileHandle) -> EvmResult<Vec<H256>> {
298 handle.record_db_read::<Runtime>(
300 32 * (<Runtime as pallet_collective::Config<Instance>>::MaxProposals::get() as usize),
301 )?;
302
303 let proposals = pallet_collective::Proposals::<Runtime, Instance>::get();
304 let proposals: Vec<_> = proposals.into_iter().map(|hash| hash.into()).collect();
305
306 Ok(proposals)
307 }
308
309 #[precompile::public("members()")]
310 #[precompile::view]
311 fn members(handle: &mut impl PrecompileHandle) -> EvmResult<Vec<Address>> {
312 handle.record_db_read::<Runtime>(
315 AccountId20::max_encoded_len()
316 * (<Runtime as pallet_collective::Config<Instance>>::MaxMembers::get() as usize),
317 )?;
318
319 let members = pallet_collective::Members::<Runtime, Instance>::get();
320 let members: Vec<_> = members.into_iter().map(|id| Address(id.into())).collect();
321
322 Ok(members)
323 }
324
325 #[precompile::public("isMember(address)")]
326 #[precompile::view]
327 fn is_member(handle: &mut impl PrecompileHandle, account: Address) -> EvmResult<bool> {
328 handle.record_db_read::<Runtime>(
331 AccountId20::max_encoded_len()
332 * (<Runtime as pallet_collective::Config<Instance>>::MaxMembers::get() as usize),
333 )?;
334
335 let account = Runtime::AddressMapping::into_account_id(account.into());
336
337 let is_member = pallet_collective::Pallet::<Runtime, Instance>::is_member(&account);
338
339 Ok(is_member)
340 }
341
342 #[precompile::public("prime()")]
343 #[precompile::view]
344 fn prime(handle: &mut impl PrecompileHandle) -> EvmResult<Address> {
345 handle.record_db_read::<Runtime>(20)?;
347
348 let prime = pallet_collective::Prime::<Runtime, Instance>::get()
349 .map(|prime| prime.into())
350 .unwrap_or(H160::zero());
351
352 Ok(Address(prime))
353 }
354}
355
356pub fn hash<Runtime>(data: &[u8]) -> H256
357where
358 Runtime: frame_system::Config,
359 H256: From<<Runtime as frame_system::Config>::Hash>,
360{
361 <Runtime as frame_system::Config>::Hashing::hash(data).into()
362}