Lines
100 %
Functions
97.92 %
Branches
// Copyright 2019-2025 PureStake Inc.
// This file is part of Moonbeam.
// Moonbeam is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Moonbeam is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>.
//! Randomness precompile unit tests
use crate::{
assert_event_emitted, mock::*, prepare_and_finish_fulfillment_gas_cost,
subcall_overhead_gas_costs,
};
use fp_evm::FeeCalculator;
use pallet_randomness::{Event as RandomnessEvent, RandomnessResults, RequestType};
use precompile_utils::{prelude::*, testing::*};
use sp_core::{H160, H256, U256};
#[test]
fn test_selector_less_than_four_bytes_reverts() {
ExtBuilder::default().build().execute_with(|| {
PrecompilesValue::get()
.prepare_test(Alice, Precompile1, vec![1u8, 2, 3])
.execute_reverts(|output| output == b"Tried to read selector out of bounds");
});
}
fn test_unimplemented_selector_reverts() {
.prepare_test(Alice, Precompile1, vec![1u8, 2, 3, 4])
.execute_reverts(|output| output == b"Unknown selector");
fn selectors() {
assert!(PCall::relay_epoch_index_selectors().contains(&0x81797566));
assert!(PCall::required_deposit_selectors().contains(&0xfb7cfdd7));
assert!(PCall::get_request_status_selectors().contains(&0xd8a4676f));
assert!(PCall::get_request_selectors().contains(&0xc58343ef));
assert!(PCall::request_local_randomness_selectors().contains(&0x9478430c));
assert!(PCall::request_babe_randomness_selectors().contains(&0x33c14a63));
assert!(PCall::fulfill_request_selectors().contains(&0x9a91eb0d));
assert!(PCall::increase_request_fee_selectors().contains(&0xd0408a7f));
assert!(PCall::purge_expired_request_selectors().contains(&0x1d26cbab));
fn modifiers() {
let mut tester =
PrecompilesModifierTester::new(PrecompilesValue::get(), Alice, Precompile1);
tester.test_view_modifier(PCall::relay_epoch_index_selectors());
tester.test_view_modifier(PCall::required_deposit_selectors());
tester.test_view_modifier(PCall::get_request_status_selectors());
tester.test_view_modifier(PCall::get_request_selectors());
tester.test_default_modifier(PCall::request_local_randomness_selectors());
tester.test_default_modifier(PCall::request_babe_randomness_selectors());
tester.test_default_modifier(PCall::fulfill_request_selectors());
tester.test_default_modifier(PCall::purge_expired_request_selectors());
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
check_precompile_implements_solidity_interfaces(&["Randomness.sol"], PCall::supports_selector)
fn relay_epoch_index_works() {
pallet_evm::AccountCodes::<Runtime>::insert(H160::from(Alice), vec![10u8]);
.prepare_test(Alice, Precompile1, PCall::relay_epoch_index {})
.execute_returns(1u64);
})
fn required_deposit_works() {
.prepare_test(Alice, Precompile1, PCall::required_deposit {})
.execute_returns(U256::from(10));
fn get_dne_request_status() {
.prepare_test(
Alice,
Precompile1,
PCall::get_request_status {
request_id: 1.into(),
},
)
.execute_returns(0u8);
fn get_pending_request_status() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000)])
.build()
.execute_with(|| {
PCall::request_babe_randomness {
refund_address: Address(Bob.into()),
fee: U256::one(),
gas_limit: 100u64,
salt: H256::default(),
num_words: 1u8,
.execute_returns(U256::zero());
request_id: 0.into(),
.execute_returns(1u8);
fn get_ready_request_status() {
PCall::request_local_randomness {
gas_limit: 10u64,
delay: 2.into(),
// run to ready block
System::set_block_number(3);
// ready status
.execute_returns(2u8);
fn get_expired_request_status() {
// run to expired block
System::set_block_number(21);
.execute_returns(3u8);
fn get_request_works() {
PCall::get_request {
.execute_returns((
U256::zero(),
Address(Bob.into()),
Address(Alice.into()),
U256::one(),
U256::from(100),
H256::default(),
1u8,
0u8,
3u32,
0u64,
21u32,
3u8,
));
fn request_babe_randomness_works() {
refund_address: Address(H160::from(Bob)),
assert_event_emitted!(RuntimeEvent::Randomness(
RandomnessEvent::RandomnessRequestedBabeEpoch {
id: 0,
refund_address: H160::from(Bob),
contract_address: H160::from(Alice),
fee: 1,
earliest_epoch: 3,
fn request_local_randomness_works() {
RandomnessEvent::RandomnessRequestedLocal {
earliest_block: 3,
fn fulfill_request_reverts_if_not_enough_gas() {
let request_gas_limit = 100u64;
let total_cost = request_gas_limit
+ subcall_overhead_gas_costs::<Runtime>().unwrap()
+ prepare_and_finish_fulfillment_gas_cost::<Runtime>(1);
gas_limit: request_gas_limit,
// fill randomness results
let mut filled_results =
RandomnessResults::<Runtime>::get(RequestType::Local(3)).unwrap();
filled_results.randomness = Some(H256::default());
RandomnessResults::<Runtime>::insert(RequestType::Local(3), filled_results);
// fulfill request
Charlie,
PCall::fulfill_request {
.with_target_gas(Some(total_cost - 1))
.with_subcall_handle(|_| panic!("should not perform subcall"))
.expect_no_logs()
.execute_reverts(|revert| revert == b"not enough gas to perform the call");
// no refund
assert_eq!(Balances::free_balance(&AccountId::from(Charlie)), 0);
fn fulfill_request_works() {
let subcall_used_gas = 50u64;
let refunded_amount = U256::from(
subcall_used_gas
+ prepare_and_finish_fulfillment_gas_cost::<Runtime>(1),
* <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price().0;
let pallet_randomness::FulfillArgs {
randomness: random_words,
..
} = pallet_randomness::Pallet::<Runtime>::prepare_fulfillment(0)
.expect("can prepare values");
let random_words: Vec<H256> = random_words.into_iter().map(|x| x.into()).collect();
.with_subcall_handle(move |subcall| {
let Subcall {
address,
transfer,
input,
target_gas,
is_static,
context,
} = subcall;
assert_eq!(context.caller, Precompile1.into());
assert_eq!(address, Alice.into());
assert_eq!(is_static, false);
assert_eq!(target_gas, Some(request_gas_limit));
assert!(transfer.is_none());
assert_eq!(context.address, Alice.into());
assert_eq!(context.apparent_value, 0u8.into());
// callback function selector: keccak256("rawFulfillRandomWords(uint256,uint256[])")
assert_eq!(
&input,
&solidity::encode_with_selector(
0x1fe543e3_u32,
(
0u64, // request id
random_words.clone()
);
SubcallOutput {
output: b"TEST".to_vec(),
cost: subcall_used_gas,
..SubcallOutput::succeed()
.with_target_gas(Some(total_cost))
.expect_log(crate::log_fulfillment_succeeded(Precompile1))
.execute_returns(());
// correctly refunded
U256::from(Balances::free_balance(&AccountId::from(Charlie))),
refunded_amount
fn fulfill_request_works_with_higher_gas() {
random_words.clone(),
.with_target_gas(Some(total_cost + 10_000))
fn fulfill_request_works_with_subcall_revert() {
..SubcallOutput::revert()
.expect_log(crate::log_fulfillment_failed(Precompile1))
fn increase_request_fee_works() {
// increase request fee
PCall::increase_request_fee {
fee_increase: 10.into(),
RandomnessEvent::RequestFeeIncreased { id: 0, new_fee: 11 }
fn purge_expired_request_works() {
// purge expired request
PCall::purge_expired_request {
RandomnessEvent::RequestExpirationExecuted { id: 0 }