1#![cfg_attr(not(feature = "std"), no_std)]
20
21extern crate alloc;
22
23use fp_evm::{Context, ExitReason, FeeCalculator, Log, PrecompileHandle};
24use frame_support::{
25 dispatch::{GetDispatchInfo, PostDispatchInfo},
26 traits::Get,
27};
28use frame_system::pallet_prelude::BlockNumberFor;
29use pallet_evm::GasWeightMapping;
30use pallet_randomness::{
31 weights::{SubstrateWeight, WeightInfo},
32 BalanceOf, GetBabeData, Pallet, Request, RequestInfo, RequestState, RequestType,
33};
34use precompile_utils::{evm::costs::call_cost, prelude::*};
35use sp_core::{H160, H256, U256};
36use sp_runtime::traits::Dispatchable;
37use sp_std::{marker::PhantomData, vec, vec::Vec};
38
39#[cfg(test)]
40pub mod mock;
41mod solidity_types;
42#[cfg(test)]
43mod tests;
44use solidity_types::*;
45
46pub fn prepare_and_finish_fulfillment_gas_cost<T: pallet_evm::Config>(num_words: u8) -> u64 {
48 <T as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
49 SubstrateWeight::<T>::prepare_fulfillment(num_words.into())
50 .saturating_add(SubstrateWeight::<T>::finish_fulfillment()),
51 )
52}
53
54pub fn subcall_overhead_gas_costs<T: pallet_evm::Config>() -> EvmResult<u64> {
55 let log_cost = log_fulfillment_failed(H160::zero())
57 .compute_cost()
58 .map_err(|_| revert("failed to compute log cost"))?;
59 let call_cost = call_cost(U256::zero(), <T as pallet_evm::Config>::config());
60 log_cost
61 .checked_add(call_cost)
62 .ok_or(revert("overflow when computing overhead gas"))
63}
64
65pub fn transaction_gas_refund<T: pallet_evm::Config>() -> u64 {
66 21_000
70 + 8 * T::config().gas_transaction_non_zero_data
71 + 24 * T::config().gas_transaction_zero_data
72}
73
74pub const LOG_FULFILLMENT_SUCCEEDED: [u8; 32] = keccak256!("FulFillmentSucceeded()");
75pub const LOG_FULFILLMENT_FAILED: [u8; 32] = keccak256!("FulFillmentFailed()");
76
77pub fn log_fulfillment_succeeded(address: impl Into<H160>) -> Log {
78 log1(address, LOG_FULFILLMENT_SUCCEEDED, vec![])
79}
80
81pub fn log_fulfillment_failed(address: impl Into<H160>) -> Log {
82 log1(address, LOG_FULFILLMENT_FAILED, vec![])
83}
84
85fn ensure_can_provide_randomness<Runtime>(
87 remaining_gas: u64,
88 request_gas_limit: u64,
89 request_fee: BalanceOf<Runtime>,
90 subcall_overhead_gas_costs: u64,
91 prepare_and_finish_fulfillment_gas_cost: u64,
92) -> EvmResult<()>
93where
94 Runtime: pallet_randomness::Config + pallet_evm::Config,
95 BalanceOf<Runtime>: Into<U256>,
96{
97 let request_gas_limit_with_overhead = request_gas_limit
98 .checked_add(subcall_overhead_gas_costs)
99 .ok_or(revert(
100 "overflow when computing request gas limit + overhead",
101 ))?;
102
103 if remaining_gas < request_gas_limit_with_overhead {
105 return Err(revert("not enough gas to perform the call"));
106 }
107
108 let total_refunded_gas = prepare_and_finish_fulfillment_gas_cost
110 .checked_add(request_gas_limit_with_overhead)
111 .ok_or(revert("overflow when computed max amount of refunded gas"))?
112 .checked_add(transaction_gas_refund::<Runtime>())
113 .ok_or(revert("overflow when computed max amount of refunded gas"))?;
114
115 let total_refunded_gas: U256 = total_refunded_gas.into();
116 let (base_fee, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
117 let execution_max_fee = total_refunded_gas.checked_mul(base_fee).ok_or(revert(
118 "gas limit (with overhead) * base fee overflowed U256",
119 ))?;
120
121 if execution_max_fee > request_fee.into() {
122 return Err(revert("request fee cannot pay for execution cost"));
123 }
124
125 Ok(())
126}
127
128fn provide_randomness(
131 handle: &mut impl PrecompileHandle,
132 request_id: u64,
133 gas_limit: u64,
134 contract: H160,
135 randomness: Vec<H256>,
136) -> EvmResult<()> {
137 let (reason, _) = handle.call(
138 contract,
139 None,
140 solidity::encode_with_selector(0x1fe543e3_u32, (request_id, randomness)),
142 Some(gas_limit),
143 false,
144 &Context {
145 caller: handle.context().address,
146 address: contract,
147 apparent_value: U256::zero(),
148 },
149 );
150 match reason {
153 ExitReason::Revert(_) | ExitReason::Error(_) => {
154 let log = log_fulfillment_failed(handle.code_address());
155 handle.record_log_costs(&[&log])?;
156 log.record(handle)?
157 }
158 ExitReason::Succeed(_) => {
159 let log = log_fulfillment_succeeded(handle.code_address());
160 handle.record_log_costs(&[&log])?;
161 log.record(handle)?
162 }
163 _ => (),
164 }
165 Ok(())
166}
167
168pub struct RandomnessPrecompile<Runtime>(PhantomData<Runtime>);
170
171#[precompile_utils::precompile]
172impl<Runtime> RandomnessPrecompile<Runtime>
173where
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 fn relay_epoch_index(handle: &mut impl PrecompileHandle) -> EvmResult<u64> {
183 handle.record_cost(1000)?;
186 let relay_epoch_index =
187 <Runtime as pallet_randomness::Config>::BabeDataGetter::get_epoch_index();
188 Ok(relay_epoch_index)
189 }
190
191 #[precompile::public("requiredDeposit()")]
192 #[precompile::view]
193 fn required_deposit(_handle: &mut impl PrecompileHandle) -> EvmResult<U256> {
194 let required_deposit: U256 = <Runtime as pallet_randomness::Config>::Deposit::get().into();
195 Ok(required_deposit)
196 }
197
198 #[precompile::public("getRequestStatus(uint256)")]
199 #[precompile::view]
200 fn get_request_status(
201 handle: &mut impl PrecompileHandle,
202 request_id: Convert<U256, u64>,
203 ) -> EvmResult<RequestStatus> {
204 let request_id = request_id.converted();
205
206 handle.record_db_read::<Runtime>(146)?;
212
213 let status =
214 if let Some(RequestState { request, .. }) = Pallet::<Runtime>::requests(request_id) {
215 handle.record_db_read::<Runtime>(8)?;
218 if request.is_expired() {
219 RequestStatus::Expired
220 } else if request.can_be_fulfilled() {
221 RequestStatus::Ready
222 } else {
223 RequestStatus::Pending
224 }
225 } else {
226 RequestStatus::DoesNotExist
227 };
228 Ok(status)
229 }
230
231 #[precompile::public("getRequest(uint256)")]
232 #[precompile::view]
233 fn get_request(
234 handle: &mut impl PrecompileHandle,
235 request_id: Convert<U256, u64>,
236 ) -> EvmResult<(
237 U256, Address, Address, U256, U256, H256, u32, RandomnessSource,
245 u32, u64, u32, u64, RequestStatus,
250 )> {
251 let request_id = request_id.converted();
252
253 handle.record_db_read::<Runtime>(146)?;
259
260 let RequestState { request, .. } =
261 Pallet::<Runtime>::requests(request_id).ok_or(revert("Request Does Not Exist"))?;
262
263 handle.record_db_read::<Runtime>(8)?;
266 let status = if request.is_expired() {
267 RequestStatus::Expired
268 } else if request.can_be_fulfilled() {
269 RequestStatus::Ready
270 } else {
271 RequestStatus::Pending
272 };
273
274 let (
275 randomness_source,
276 fulfillment_block,
277 fulfillment_epoch,
278 expiration_block,
279 expiration_epoch,
280 request_status,
281 ) = 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 RequestInfo::Local(block_due, block_expired) => (
291 RandomnessSource::LocalVRF,
292 block_due
293 .try_into()
294 .map_err(|_| revert("block number overflowed u32"))?,
295 0u64,
296 block_expired
297 .try_into()
298 .map_err(|_| revert("block number overflowed u32"))?,
299 0u64,
300 status,
301 ),
302 };
303
304 let (refund_address, contract_address, fee): (Address, Address, U256) = (
305 request.refund_address.into(),
306 request.contract_address.into(),
307 request.fee.into(),
308 );
309
310 Ok((
311 request_id.into(),
312 refund_address.into(),
313 contract_address,
314 fee,
315 request.gas_limit.into(),
316 request.salt,
317 request.num_words.into(),
318 randomness_source,
319 fulfillment_block,
320 fulfillment_epoch,
321 expiration_block,
322 expiration_epoch,
323 request_status,
324 ))
325 }
326
327 #[precompile::public("requestRelayBabeEpochRandomWords(address,uint256,uint64,bytes32,uint8)")]
329 fn request_babe_randomness(
330 handle: &mut impl PrecompileHandle,
331 refund_address: Address,
332 fee: U256,
333 gas_limit: u64,
334 salt: H256,
335 num_words: u8,
336 ) -> EvmResult<U256> {
337 handle.record_cost(500)?;
339
340 let refund_address: H160 = refund_address.into();
341 let fee: BalanceOf<Runtime> = fee
342 .try_into()
343 .map_err(|_| RevertReason::value_is_too_large("balance type").in_field("fee"))?;
344
345 let contract_address = handle.context().caller;
346
347 let two_epochs_later =
348 <Runtime as pallet_randomness::Config>::BabeDataGetter::get_epoch_index()
349 .checked_add(2u64)
350 .ok_or(revert("Epoch Index (u64) overflowed"))?;
351
352 let request = Request {
353 refund_address,
354 contract_address,
355 fee,
356 gas_limit,
357 num_words,
358 salt,
359 info: RequestType::BabeEpoch(two_epochs_later),
360 };
361
362 let request_randomness_weight =
363 <<Runtime as pallet_randomness::Config>::WeightInfo>::request_randomness();
364 RuntimeHelper::<Runtime>::record_external_cost(handle, request_randomness_weight, 0)?;
365 let request_id = Pallet::<Runtime>::request_randomness(request)
366 .map_err(|e| revert(alloc::format!("Error in pallet_randomness: {:?}", e)))?;
367 RuntimeHelper::<Runtime>::refund_weight_v2_cost(handle, request_randomness_weight, None)?;
368
369 Ok(request_id.into())
370 }
371 #[precompile::public("requestLocalVRFRandomWords(address,uint256,uint64,bytes32,uint8,uint64)")]
373 fn request_local_randomness(
374 handle: &mut impl PrecompileHandle,
375 refund_address: Address,
376 fee: U256,
377 gas_limit: u64,
378 salt: H256,
379 num_words: u8,
380 delay: Convert<u64, u32>,
381 ) -> EvmResult<U256> {
382 handle.record_cost(500)?;
384
385 let refund_address: H160 = refund_address.into();
386 let fee: BalanceOf<Runtime> = fee
387 .try_into()
388 .map_err(|_| RevertReason::value_is_too_large("balance type").in_field("fee"))?;
389
390 let contract_address = handle.context().caller;
391
392 let current_block_number: u32 = <frame_system::Pallet<Runtime>>::block_number()
393 .try_into()
394 .map_err(|_| revert("block number overflowed u32"))?;
395
396 let requested_block_number = delay
397 .converted()
398 .checked_add(current_block_number)
399 .ok_or(revert("addition result overflowed u64"))?
400 .try_into()
401 .map_err(|_| revert("u64 addition result overflowed block number type"))?;
402
403 let request = Request {
404 refund_address,
405 contract_address,
406 fee,
407 gas_limit,
408 num_words,
409 salt,
410 info: RequestType::Local(requested_block_number),
411 };
412
413 let request_randomness_weight =
414 <<Runtime as pallet_randomness::Config>::WeightInfo>::request_randomness();
415 RuntimeHelper::<Runtime>::record_external_cost(handle, request_randomness_weight, 0)?;
416 let request_id = Pallet::<Runtime>::request_randomness(request)
417 .map_err(|e| revert(alloc::format!("Error in pallet_randomness: {:?}", e)))?;
418 RuntimeHelper::<Runtime>::refund_weight_v2_cost(handle, request_randomness_weight, None)?;
419
420 Ok(request_id.into())
421 }
422
423 #[precompile::public("fulfillRequest(uint256)")]
425 fn fulfill_request(
426 handle: &mut impl PrecompileHandle,
427 request_id: Convert<U256, u64>,
428 ) -> EvmResult {
429 let request_id = request_id.converted();
430
431 let prepare_fulfillment_max_weight =
433 <<Runtime as pallet_randomness::Config>::WeightInfo>::prepare_fulfillment(
434 <Runtime as pallet_randomness::Config>::MaxRandomWords::get() as u32,
435 );
436 RuntimeHelper::<Runtime>::record_external_cost(handle, prepare_fulfillment_max_weight, 0)?;
437 let pallet_randomness::FulfillArgs {
438 request,
439 deposit,
440 randomness,
441 } = Pallet::<Runtime>::prepare_fulfillment(request_id)
442 .map_err(|e| revert(alloc::format!("{:?}", e)))?;
443 let prepare_fulfillment_actual_weight =
444 <<Runtime as pallet_randomness::Config>::WeightInfo>::prepare_fulfillment(
445 request.num_words as u32,
446 );
447 let mut prepare_and_finish_fulfillment_used_gas =
448 RuntimeHelper::<Runtime>::refund_weight_v2_cost(
449 handle,
450 prepare_fulfillment_max_weight,
451 Some(prepare_fulfillment_actual_weight),
452 )?;
453
454 let subcall_overhead_gas_costs = subcall_overhead_gas_costs::<Runtime>()?;
455
456 let finish_fulfillment_weight =
459 <<Runtime as pallet_randomness::Config>::WeightInfo>::finish_fulfillment();
460 RuntimeHelper::<Runtime>::record_external_cost(handle, finish_fulfillment_weight, 0)?;
461 prepare_and_finish_fulfillment_used_gas += RuntimeHelper::<Runtime>::refund_weight_v2_cost(
462 handle,
463 finish_fulfillment_weight,
464 None,
465 )?;
466
467 ensure_can_provide_randomness::<Runtime>(
469 handle.remaining_gas(),
470 request.gas_limit,
471 request.fee,
472 subcall_overhead_gas_costs,
473 prepare_and_finish_fulfillment_used_gas,
474 )?;
475
476 let remaining_gas_before = handle.remaining_gas();
480 provide_randomness(
481 handle,
482 request_id,
483 request.gas_limit,
484 request.contract_address.clone().into(),
485 randomness.into_iter().map(|x| H256(x)).collect(),
486 )?;
487 let remaining_gas_after = handle.remaining_gas();
488
489 let gas_used: U256 = remaining_gas_before
492 .checked_sub(remaining_gas_after)
493 .ok_or(revert("Before remaining gas < After remaining gas"))?
494 .checked_add(prepare_and_finish_fulfillment_used_gas)
495 .ok_or(revert("overflow when adding real call cost + overhead"))?
496 .checked_add(transaction_gas_refund::<Runtime>())
497 .ok_or(revert("overflow when adding real call cost + overhead"))?
498 .into();
499 let (base_fee, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
500 let cost_of_execution: BalanceOf<Runtime> = gas_used
501 .checked_mul(base_fee)
502 .ok_or(revert("Multiply gas used by base fee overflowed"))?
503 .try_into()
504 .map_err(|_| revert("amount is too large for provided balance type"))?;
505
506 Pallet::<Runtime>::finish_fulfillment(
511 request_id,
512 request,
513 deposit,
514 &handle.context().caller,
515 cost_of_execution,
516 );
517
518 Ok(())
519 }
520
521 #[precompile::public("increaseRequestFee(uint256,uint256)")]
523 fn increase_request_fee(
524 handle: &mut impl PrecompileHandle,
525 request_id: Convert<U256, u64>,
526 fee_increase: U256,
527 ) -> EvmResult {
528 let increase_fee_weight =
529 <<Runtime as pallet_randomness::Config>::WeightInfo>::increase_fee();
530 RuntimeHelper::<Runtime>::record_external_cost(handle, increase_fee_weight, 0)?;
531
532 let request_id = request_id.converted();
533
534 let fee_increase: BalanceOf<Runtime> = fee_increase.try_into().map_err(|_| {
535 RevertReason::value_is_too_large("balance type").in_field("feeIncrease")
536 })?;
537
538 Pallet::<Runtime>::increase_request_fee(&handle.context().caller, request_id, fee_increase)
539 .map_err(|e| revert(alloc::format!("{:?}", e)))?;
540
541 RuntimeHelper::<Runtime>::refund_weight_v2_cost(handle, increase_fee_weight, None)?;
542
543 Ok(())
544 }
545 #[precompile::public("purgeExpiredRequest(uint256)")]
548 fn purge_expired_request(
549 handle: &mut impl PrecompileHandle,
550 request_id: Convert<U256, u64>,
551 ) -> EvmResult {
552 let execute_request_expiration_weight =
553 <<Runtime as pallet_randomness::Config>::WeightInfo>::execute_request_expiration();
554 RuntimeHelper::<Runtime>::record_external_cost(
555 handle,
556 execute_request_expiration_weight,
557 0,
558 )?;
559
560 let request_id = request_id.converted();
561
562 Pallet::<Runtime>::execute_request_expiration(&handle.context().caller, request_id)
563 .map_err(|e| revert(alloc::format!("{:?}", e)))?;
564 RuntimeHelper::<Runtime>::refund_weight_v2_cost(
565 handle,
566 execute_request_expiration_weight,
567 None,
568 )?;
569
570 Ok(())
571 }
572}