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 randomness through an evm precompile.
18

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

            
21
extern crate alloc;
22

            
23
use fp_evm::{Context, ExitReason, FeeCalculator, Log, PrecompileHandle};
24
use frame_support::{
25
	dispatch::{GetDispatchInfo, PostDispatchInfo},
26
	traits::Get,
27
};
28
use frame_system::pallet_prelude::BlockNumberFor;
29
use pallet_evm::GasWeightMapping;
30
use pallet_randomness::{
31
	weights::{SubstrateWeight, WeightInfo},
32
	BalanceOf, GetBabeData, Pallet, Request, RequestInfo, RequestState, RequestType,
33
};
34
use precompile_utils::{evm::costs::call_cost, prelude::*};
35
use sp_core::{H160, H256, U256};
36
use sp_runtime::traits::Dispatchable;
37
use sp_std::{marker::PhantomData, vec, vec::Vec};
38

            
39
#[cfg(test)]
40
pub mod mock;
41
mod solidity_types;
42
#[cfg(test)]
43
mod tests;
44
use solidity_types::*;
45

            
46
/// Fulfillment overhead cost, which takes input weight hint -> weight -> return gas
47
7
pub fn prepare_and_finish_fulfillment_gas_cost<T: pallet_evm::Config>(num_words: u8) -> u64 {
48
7
	<T as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
49
7
		SubstrateWeight::<T>::prepare_fulfillment(num_words.into())
50
7
			.saturating_add(SubstrateWeight::<T>::finish_fulfillment()),
51
7
	)
52
7
}
53

            
54
11
pub fn subcall_overhead_gas_costs<T: pallet_evm::Config>() -> EvmResult<u64> {
55
	// cost of log don't depend on specific address.
56
11
	let log_cost = log_fulfillment_failed(H160::zero())
57
11
		.compute_cost()
58
11
		.map_err(|_| revert("failed to compute log cost"))?;
59
11
	let call_cost = call_cost(U256::zero(), <T as pallet_evm::Config>::config());
60
11
	log_cost
61
11
		.checked_add(call_cost)
62
11
		.ok_or(revert("overflow when computing overhead gas"))
63
11
}
64

            
65
6
pub fn transaction_gas_refund<T: pallet_evm::Config>() -> u64 {
66
6
	// 21_000 for the transaction itself
67
6
	// we also include the fees to pay for input request id which is 32 bytes, which is in practice
68
6
	// a u64 and thus can only occupy 8 non zero bytes.
69
6
	21_000
70
6
		+ 8 * T::config().gas_transaction_non_zero_data
71
6
		+ 24 * T::config().gas_transaction_zero_data
72
6
}
73

            
74
pub const LOG_FULFILLMENT_SUCCEEDED: [u8; 32] = keccak256!("FulFillmentSucceeded()");
75
pub const LOG_FULFILLMENT_FAILED: [u8; 32] = keccak256!("FulFillmentFailed()");
76

            
77
4
pub fn log_fulfillment_succeeded(address: impl Into<H160>) -> Log {
78
4
	log1(address, LOG_FULFILLMENT_SUCCEEDED, vec![])
79
4
}
80

            
81
13
pub fn log_fulfillment_failed(address: impl Into<H160>) -> Log {
82
13
	log1(address, LOG_FULFILLMENT_FAILED, vec![])
83
13
}
84

            
85
/// Reverts if fees and gas_limit are not sufficient to make subcall and cleanup
86
4
fn ensure_can_provide_randomness<Runtime>(
87
4
	remaining_gas: u64,
88
4
	request_gas_limit: u64,
89
4
	request_fee: BalanceOf<Runtime>,
90
4
	subcall_overhead_gas_costs: u64,
91
4
	prepare_and_finish_fulfillment_gas_cost: u64,
92
4
) -> EvmResult<()>
93
4
where
94
4
	Runtime: pallet_randomness::Config + pallet_evm::Config,
95
4
	BalanceOf<Runtime>: Into<U256>,
96
4
{
97
4
	let request_gas_limit_with_overhead = request_gas_limit
98
4
		.checked_add(subcall_overhead_gas_costs)
99
4
		.ok_or(revert(
100
4
			"overflow when computing request gas limit + overhead",
101
4
		))?;
102

            
103
	// Ensure precompile have enough gas to perform subcall with the overhead.
104
4
	if remaining_gas < request_gas_limit_with_overhead {
105
1
		return Err(revert("not enough gas to perform the call"));
106
3
	}
107

            
108
	// Ensure request fee is enough to refund the fulfiller.
109
3
	let total_refunded_gas = prepare_and_finish_fulfillment_gas_cost
110
3
		.checked_add(request_gas_limit_with_overhead)
111
3
		.ok_or(revert("overflow when computed max amount of refunded gas"))?
112
3
		.checked_add(transaction_gas_refund::<Runtime>())
113
3
		.ok_or(revert("overflow when computed max amount of refunded gas"))?;
114

            
115
3
	let total_refunded_gas: U256 = total_refunded_gas.into();
116
3
	let (base_fee, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
117
3
	let execution_max_fee = total_refunded_gas.checked_mul(base_fee).ok_or(revert(
118
3
		"gas limit (with overhead) * base fee overflowed U256",
119
3
	))?;
120

            
121
3
	if execution_max_fee > request_fee.into() {
122
		return Err(revert("request fee cannot pay for execution cost"));
123
3
	}
124
3

            
125
3
	Ok(())
126
4
}
127

            
128
/// Subcall to provide randomness
129
/// caller must call `ensure_can_provide_randomness` before calling this function
130
3
fn provide_randomness(
131
3
	handle: &mut impl PrecompileHandle,
132
3
	request_id: u64,
133
3
	gas_limit: u64,
134
3
	contract: H160,
135
3
	randomness: Vec<H256>,
136
3
) -> EvmResult<()> {
137
3
	let (reason, _) = handle.call(
138
3
		contract,
139
3
		None,
140
3
		// callback function selector: keccak256("rawFulfillRandomWords(uint256,uint256[])")
141
3
		solidity::encode_with_selector(0x1fe543e3_u32, (request_id, randomness)),
142
3
		Some(gas_limit),
143
3
		false,
144
3
		&Context {
145
3
			caller: handle.context().address,
146
3
			address: contract,
147
3
			apparent_value: U256::zero(),
148
3
		},
149
3
	);
150
3
	// Logs
151
3
	// We reserved enough gas so this should not OOG.
152
3
	match reason {
153
		ExitReason::Revert(_) | ExitReason::Error(_) => {
154
1
			let log = log_fulfillment_failed(handle.code_address());
155
1
			handle.record_log_costs(&[&log])?;
156
1
			log.record(handle)?
157
		}
158
		ExitReason::Succeed(_) => {
159
2
			let log = log_fulfillment_succeeded(handle.code_address());
160
2
			handle.record_log_costs(&[&log])?;
161
2
			log.record(handle)?
162
		}
163
		_ => (),
164
	}
165
3
	Ok(())
166
3
}
167

            
168
/// A precompile to wrap the functionality from pallet-randomness
169
pub struct RandomnessPrecompile<Runtime>(PhantomData<Runtime>);
170

            
171
214
#[precompile_utils::precompile]
172
impl<Runtime> RandomnessPrecompile<Runtime>
173
where
174
	Runtime: pallet_randomness::Config + pallet_evm::Config,
175
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
176
	Runtime::RuntimeCall: From<pallet_randomness::Call<Runtime>>,
177
	BlockNumberFor<Runtime>: TryInto<u32> + TryFrom<u32>,
178
	BalanceOf<Runtime>: TryFrom<U256> + Into<U256>,
179
{
180
	#[precompile::public("relayEpochIndex()")]
181
	#[precompile::view]
182
2
	fn relay_epoch_index(handle: &mut impl PrecompileHandle) -> EvmResult<u64> {
183
2
		// No DB access but lot of logical stuff
184
2
		// To prevent spam, we charge an arbitrary amount of gas
185
2
		handle.record_cost(1000)?;
186
2
		let relay_epoch_index =
187
2
			<Runtime as pallet_randomness::Config>::BabeDataGetter::get_epoch_index();
188
2
		Ok(relay_epoch_index)
189
2
	}
190

            
191
	#[precompile::public("requiredDeposit()")]
192
	#[precompile::view]
193
2
	fn required_deposit(_handle: &mut impl PrecompileHandle) -> EvmResult<U256> {
194
2
		let required_deposit: U256 = <Runtime as pallet_randomness::Config>::Deposit::get().into();
195
2
		Ok(required_deposit)
196
2
	}
197

            
198
	#[precompile::public("getRequestStatus(uint256)")]
199
	#[precompile::view]
200
4
	fn get_request_status(
201
4
		handle: &mut impl PrecompileHandle,
202
4
		request_id: Convert<U256, u64>,
203
4
	) -> EvmResult<RequestStatus> {
204
4
		let request_id = request_id.converted();
205
4

            
206
4
		// Storage item read: pallet_randomness::Requests
207
4
		// Max encoded len: Twox64(8) + RequestId(8) + RequestState(
208
4
		// 	request(refund_address(20)+contract_address(20)+fee(16)+gas_limit(8)+num_words(1)
209
4
		//   +salt(32)+info(17))
210
4
		// + deposit(16) )
211
4
		handle.record_db_read::<Runtime>(146)?;
212

            
213
4
		let status =
214
4
			if let Some(RequestState { request, .. }) = Pallet::<Runtime>::requests(request_id) {
215
				// Storage item read: pallet_randomness::RelayEpoch
216
				// Max encoded len: u64(8)
217
3
				handle.record_db_read::<Runtime>(8)?;
218
3
				if request.is_expired() {
219
1
					RequestStatus::Expired
220
2
				} else if request.can_be_fulfilled() {
221
1
					RequestStatus::Ready
222
				} else {
223
1
					RequestStatus::Pending
224
				}
225
			} else {
226
1
				RequestStatus::DoesNotExist
227
			};
228
4
		Ok(status)
229
4
	}
230

            
231
	#[precompile::public("getRequest(uint256)")]
232
	#[precompile::view]
233
1
	fn get_request(
234
1
		handle: &mut impl PrecompileHandle,
235
1
		request_id: Convert<U256, u64>,
236
1
	) -> EvmResult<(
237
1
		U256,    // id
238
1
		Address, // refund address
239
1
		Address, // contract address
240
1
		U256,    // fee
241
1
		U256,    // gas limit
242
1
		H256,    // salt
243
1
		u32,     // num words
244
1
		RandomnessSource,
245
1
		u32, // fulfillment block
246
1
		u64, // fulfullment epoch index
247
1
		u32, // expiration block
248
1
		u64, // expiration epoch index
249
1
		RequestStatus,
250
1
	)> {
251
1
		let request_id = request_id.converted();
252
1

            
253
1
		// Storage item read: pallet_randomness::Requests
254
1
		// Max encoded len: Twox64(8) + RequestId(8) + RequestState(
255
1
		// 	request(refund_address(20)+contract_address(20)+fee(16)+gas_limit(8)+num_words(1)
256
1
		//   +salt(32)+info(17))
257
1
		// + deposit(16) )
258
1
		handle.record_db_read::<Runtime>(146)?;
259

            
260
1
		let RequestState { request, .. } =
261
1
			Pallet::<Runtime>::requests(request_id).ok_or(revert("Request Does Not Exist"))?;
262

            
263
		// Storage item read: pallet_randomness::RelayEpoch
264
		// Max encoded len: u64(8)
265
1
		handle.record_db_read::<Runtime>(8)?;
266
1
		let status = if request.is_expired() {
267
1
			RequestStatus::Expired
268
		} else if request.can_be_fulfilled() {
269
			RequestStatus::Ready
270
		} else {
271
			RequestStatus::Pending
272
		};
273

            
274
		let (
275
1
			randomness_source,
276
1
			fulfillment_block,
277
1
			fulfillment_epoch,
278
1
			expiration_block,
279
1
			expiration_epoch,
280
1
			request_status,
281
1
		) = match request.info {
282
			RequestInfo::BabeEpoch(epoch_due, epoch_expired) => (
283
				RandomnessSource::RelayBabeEpoch,
284
				0u32,
285
				epoch_due,
286
				0u32,
287
				epoch_expired,
288
				status,
289
			),
290
1
			RequestInfo::Local(block_due, block_expired) => (
291
1
				RandomnessSource::LocalVRF,
292
1
				block_due
293
1
					.try_into()
294
1
					.map_err(|_| revert("block number overflowed u32"))?,
295
				0u64,
296
1
				block_expired
297
1
					.try_into()
298
1
					.map_err(|_| revert("block number overflowed u32"))?,
299
				0u64,
300
1
				status,
301
			),
302
		};
303

            
304
1
		let (refund_address, contract_address, fee): (Address, Address, U256) = (
305
1
			request.refund_address.into(),
306
1
			request.contract_address.into(),
307
1
			request.fee.into(),
308
1
		);
309
1

            
310
1
		Ok((
311
1
			request_id.into(),
312
1
			refund_address.into(),
313
1
			contract_address,
314
1
			fee,
315
1
			request.gas_limit.into(),
316
1
			request.salt,
317
1
			request.num_words.into(),
318
1
			randomness_source,
319
1
			fulfillment_block,
320
1
			fulfillment_epoch,
321
1
			expiration_block,
322
1
			expiration_epoch,
323
1
			request_status,
324
1
		))
325
1
	}
326

            
327
	/// Make request for babe randomness one epoch ago
328
	#[precompile::public("requestRelayBabeEpochRandomWords(address,uint256,uint64,bytes32,uint8)")]
329
2
	fn request_babe_randomness(
330
2
		handle: &mut impl PrecompileHandle,
331
2
		refund_address: Address,
332
2
		fee: U256,
333
2
		gas_limit: u64,
334
2
		salt: H256,
335
2
		num_words: u8,
336
2
	) -> EvmResult<U256> {
337
2
		// Until proper benchmark, charge few hardcoded gas to prevent free spam
338
2
		handle.record_cost(500)?;
339

            
340
2
		let refund_address: H160 = refund_address.into();
341
2
		let fee: BalanceOf<Runtime> = fee
342
2
			.try_into()
343
2
			.map_err(|_| RevertReason::value_is_too_large("balance type").in_field("fee"))?;
344

            
345
2
		let contract_address = handle.context().caller;
346

            
347
2
		let two_epochs_later =
348
2
			<Runtime as pallet_randomness::Config>::BabeDataGetter::get_epoch_index()
349
2
				.checked_add(2u64)
350
2
				.ok_or(revert("Epoch Index (u64) overflowed"))?;
351

            
352
2
		let request = Request {
353
2
			refund_address,
354
2
			contract_address,
355
2
			fee,
356
2
			gas_limit,
357
2
			num_words,
358
2
			salt,
359
2
			info: RequestType::BabeEpoch(two_epochs_later),
360
2
		};
361
2

            
362
2
		let request_randomness_weight =
363
2
			<<Runtime as pallet_randomness::Config>::WeightInfo>::request_randomness();
364
2
		RuntimeHelper::<Runtime>::record_external_cost(handle, request_randomness_weight, 0)?;
365
2
		let request_id = Pallet::<Runtime>::request_randomness(request)
366
2
			.map_err(|e| revert(alloc::format!("Error in pallet_randomness: {:?}", e)))?;
367
2
		RuntimeHelper::<Runtime>::refund_weight_v2_cost(handle, request_randomness_weight, None)?;
368

            
369
2
		Ok(request_id.into())
370
2
	}
371
	/// Make request for local VRF randomness
372
	#[precompile::public("requestLocalVRFRandomWords(address,uint256,uint64,bytes32,uint8,uint64)")]
373
10
	fn request_local_randomness(
374
10
		handle: &mut impl PrecompileHandle,
375
10
		refund_address: Address,
376
10
		fee: U256,
377
10
		gas_limit: u64,
378
10
		salt: H256,
379
10
		num_words: u8,
380
10
		delay: Convert<u64, u32>,
381
10
	) -> EvmResult<U256> {
382
10
		// Until proper benchmark, charge few hardcoded gas to prevent free spam
383
10
		handle.record_cost(500)?;
384

            
385
10
		let refund_address: H160 = refund_address.into();
386
10
		let fee: BalanceOf<Runtime> = fee
387
10
			.try_into()
388
10
			.map_err(|_| RevertReason::value_is_too_large("balance type").in_field("fee"))?;
389

            
390
10
		let contract_address = handle.context().caller;
391

            
392
10
		let current_block_number: u32 = <frame_system::Pallet<Runtime>>::block_number()
393
10
			.try_into()
394
10
			.map_err(|_| revert("block number overflowed u32"))?;
395

            
396
10
		let requested_block_number = delay
397
10
			.converted()
398
10
			.checked_add(current_block_number)
399
10
			.ok_or(revert("addition result overflowed u64"))?
400
10
			.try_into()
401
10
			.map_err(|_| revert("u64 addition result overflowed block number type"))?;
402

            
403
10
		let request = Request {
404
10
			refund_address,
405
10
			contract_address,
406
10
			fee,
407
10
			gas_limit,
408
10
			num_words,
409
10
			salt,
410
10
			info: RequestType::Local(requested_block_number),
411
10
		};
412
10

            
413
10
		let request_randomness_weight =
414
10
			<<Runtime as pallet_randomness::Config>::WeightInfo>::request_randomness();
415
10
		RuntimeHelper::<Runtime>::record_external_cost(handle, request_randomness_weight, 0)?;
416
10
		let request_id = Pallet::<Runtime>::request_randomness(request)
417
10
			.map_err(|e| revert(alloc::format!("Error in pallet_randomness: {:?}", e)))?;
418
10
		RuntimeHelper::<Runtime>::refund_weight_v2_cost(handle, request_randomness_weight, None)?;
419

            
420
10
		Ok(request_id.into())
421
10
	}
422

            
423
	/// Fulfill a randomness request due to be fulfilled
424
	#[precompile::public("fulfillRequest(uint256)")]
425
4
	fn fulfill_request(
426
4
		handle: &mut impl PrecompileHandle,
427
4
		request_id: Convert<U256, u64>,
428
4
	) -> EvmResult {
429
4
		let request_id = request_id.converted();
430
4

            
431
4
		// Call `prepare_fulfillment`, prevently charge for MaxRandomWords then refund.
432
4
		let prepare_fulfillment_max_weight =
433
4
			<<Runtime as pallet_randomness::Config>::WeightInfo>::prepare_fulfillment(
434
4
				<Runtime as pallet_randomness::Config>::MaxRandomWords::get() as u32,
435
4
			);
436
4
		RuntimeHelper::<Runtime>::record_external_cost(handle, prepare_fulfillment_max_weight, 0)?;
437
		let pallet_randomness::FulfillArgs {
438
4
			request,
439
4
			deposit,
440
4
			randomness,
441
4
		} = Pallet::<Runtime>::prepare_fulfillment(request_id)
442
4
			.map_err(|e| revert(alloc::format!("{:?}", e)))?;
443
4
		let prepare_fulfillment_actual_weight =
444
4
			<<Runtime as pallet_randomness::Config>::WeightInfo>::prepare_fulfillment(
445
4
				request.num_words as u32,
446
4
			);
447
4
		let mut prepare_and_finish_fulfillment_used_gas =
448
4
			RuntimeHelper::<Runtime>::refund_weight_v2_cost(
449
4
				handle,
450
4
				prepare_fulfillment_max_weight,
451
4
				Some(prepare_fulfillment_actual_weight),
452
4
			)?;
453

            
454
4
		let subcall_overhead_gas_costs = subcall_overhead_gas_costs::<Runtime>()?;
455

            
456
		// Precharge for finish fullfillment (necessary to be able to compute
457
		// prepare_and_finish_fulfillment_used_gas)
458
4
		let finish_fulfillment_weight =
459
4
			<<Runtime as pallet_randomness::Config>::WeightInfo>::finish_fulfillment();
460
4
		RuntimeHelper::<Runtime>::record_external_cost(handle, finish_fulfillment_weight, 0)?;
461
4
		prepare_and_finish_fulfillment_used_gas += RuntimeHelper::<Runtime>::refund_weight_v2_cost(
462
4
			handle,
463
4
			finish_fulfillment_weight,
464
4
			None,
465
4
		)?;
466

            
467
		// check that randomness can be provided
468
4
		ensure_can_provide_randomness::<Runtime>(
469
4
			handle.remaining_gas(),
470
4
			request.gas_limit,
471
4
			request.fee,
472
4
			subcall_overhead_gas_costs,
473
4
			prepare_and_finish_fulfillment_used_gas,
474
4
		)?;
475

            
476
		// We meter this section to know how much gas was actually used.
477
		// It contains the gas used by the subcall and the overhead actually
478
		// performing a call. It doesn't contain `prepare_and_finish_fulfillment_used_gas`.
479
3
		let remaining_gas_before = handle.remaining_gas();
480
3
		provide_randomness(
481
3
			handle,
482
3
			request_id,
483
3
			request.gas_limit,
484
3
			request.contract_address.clone().into(),
485
3
			randomness.into_iter().map(|x| H256(x)).collect(),
486
3
		)?;
487
3
		let remaining_gas_after = handle.remaining_gas();
488

            
489
		// We compute the actual gas used to refund the caller.
490
		// It is the metered gas + `prepare_and_finish_fulfillment_used_gas`.
491
3
		let gas_used: U256 = remaining_gas_before
492
3
			.checked_sub(remaining_gas_after)
493
3
			.ok_or(revert("Before remaining gas < After remaining gas"))?
494
3
			.checked_add(prepare_and_finish_fulfillment_used_gas)
495
3
			.ok_or(revert("overflow when adding real call cost + overhead"))?
496
3
			.checked_add(transaction_gas_refund::<Runtime>())
497
3
			.ok_or(revert("overflow when adding real call cost + overhead"))?
498
3
			.into();
499
3
		let (base_fee, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
500
3
		let cost_of_execution: BalanceOf<Runtime> = gas_used
501
3
			.checked_mul(base_fee)
502
3
			.ok_or(revert("Multiply gas used by base fee overflowed"))?
503
3
			.try_into()
504
3
			.map_err(|_| revert("amount is too large for provided balance type"))?;
505

            
506
		// Finish fulfillment to
507
		// refund cost of execution to caller
508
		// refund excess fee to the refund_address
509
		// remove request state
510
3
		Pallet::<Runtime>::finish_fulfillment(
511
3
			request_id,
512
3
			request,
513
3
			deposit,
514
3
			&handle.context().caller,
515
3
			cost_of_execution,
516
3
		);
517
3

            
518
3
		Ok(())
519
4
	}
520

            
521
	/// Increase the fee used to refund fulfillment of the request
522
	#[precompile::public("increaseRequestFee(uint256,uint256)")]
523
1
	fn increase_request_fee(
524
1
		handle: &mut impl PrecompileHandle,
525
1
		request_id: Convert<U256, u64>,
526
1
		fee_increase: U256,
527
1
	) -> EvmResult {
528
1
		let increase_fee_weight =
529
1
			<<Runtime as pallet_randomness::Config>::WeightInfo>::increase_fee();
530
1
		RuntimeHelper::<Runtime>::record_external_cost(handle, increase_fee_weight, 0)?;
531

            
532
1
		let request_id = request_id.converted();
533

            
534
1
		let fee_increase: BalanceOf<Runtime> = fee_increase.try_into().map_err(|_| {
535
			RevertReason::value_is_too_large("balance type").in_field("feeIncrease")
536
1
		})?;
537

            
538
1
		Pallet::<Runtime>::increase_request_fee(&handle.context().caller, request_id, fee_increase)
539
1
			.map_err(|e| revert(alloc::format!("{:?}", e)))?;
540

            
541
1
		RuntimeHelper::<Runtime>::refund_weight_v2_cost(handle, increase_fee_weight, None)?;
542

            
543
1
		Ok(())
544
1
	}
545
	/// Execute request expiration to remove the request from storage
546
	/// Transfers `fee` to caller and `deposit` back to `contract_address`
547
	#[precompile::public("purgeExpiredRequest(uint256)")]
548
1
	fn purge_expired_request(
549
1
		handle: &mut impl PrecompileHandle,
550
1
		request_id: Convert<U256, u64>,
551
1
	) -> EvmResult {
552
1
		let execute_request_expiration_weight =
553
1
			<<Runtime as pallet_randomness::Config>::WeightInfo>::execute_request_expiration();
554
1
		RuntimeHelper::<Runtime>::record_external_cost(
555
1
			handle,
556
1
			execute_request_expiration_weight,
557
1
			0,
558
1
		)?;
559

            
560
1
		let request_id = request_id.converted();
561
1

            
562
1
		Pallet::<Runtime>::execute_request_expiration(&handle.context().caller, request_id)
563
1
			.map_err(|e| revert(alloc::format!("{:?}", e)))?;
564
1
		RuntimeHelper::<Runtime>::refund_weight_v2_cost(
565
1
			handle,
566
1
			execute_request_expiration_weight,
567
1
			None,
568
1
		)?;
569

            
570
1
		Ok(())
571
1
	}
572
}