mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 13:31:10 +00:00
Use parameter_types instead of thread_local for test-setup (#12036)
* Edit to Assets. parameter_types * fixes * Test Fixes. WIP * Edits to pallet-aura * Camel Case Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Implementation of mutate fn * update to pallet-aura * Update to frame-system. Fixes * Update to frame-support-test. CamelCases * Updates to frame- contracts, offences, staking, bounties, child bounties * Edit to mutate fn. Changes to frame-contracts. CamelCase pallet-aura * Edits to frame-contracts & executive * cargo +nightly fmt * unused import removed * unused import removed * cargo +nightly fmt * minor adjustment * updates * updates * cargo +nightly fmt * cargo +nightly fmt * take fn implemented * update * update * Fixes to CallFilter * cargo +nightly fmt * final fixes * Default changed to $value * Update frame/support/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -1376,7 +1376,7 @@ mod tests {
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
use frame_support::{assert_err, assert_ok, parameter_types};
|
||||
use frame_system::{EventRecord, Phase};
|
||||
use hex_literal::hex;
|
||||
use pallet_contracts_primitives::ReturnFlags;
|
||||
@@ -1393,8 +1393,8 @@ mod tests {
|
||||
|
||||
type MockStack<'a> = Stack<'a, Test, MockExecutable>;
|
||||
|
||||
thread_local! {
|
||||
static LOADER: RefCell<MockLoader> = RefCell::new(MockLoader::default());
|
||||
parameter_types! {
|
||||
static Loader: MockLoader = MockLoader::default();
|
||||
}
|
||||
|
||||
fn events() -> Vec<Event<Test>> {
|
||||
@@ -1420,8 +1420,8 @@ mod tests {
|
||||
refcount: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MockLoader {
|
||||
#[derive(Default, Clone)]
|
||||
pub struct MockLoader {
|
||||
map: HashMap<CodeHash<Test>, MockExecutable>,
|
||||
counter: u64,
|
||||
}
|
||||
@@ -1431,8 +1431,7 @@ mod tests {
|
||||
func_type: ExportedFunction,
|
||||
f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static,
|
||||
) -> CodeHash<Test> {
|
||||
LOADER.with(|loader| {
|
||||
let mut loader = loader.borrow_mut();
|
||||
Loader::mutate(|loader| {
|
||||
// Generate code hashes as monotonically increasing values.
|
||||
let hash = <Test as frame_system::Config>::Hash::from_low_u64_be(loader.counter);
|
||||
loader.counter += 1;
|
||||
@@ -1445,8 +1444,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn increment_refcount(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
|
||||
LOADER.with(|loader| {
|
||||
let mut loader = loader.borrow_mut();
|
||||
Loader::mutate(|loader| {
|
||||
match loader.map.entry(code_hash) {
|
||||
Entry::Vacant(_) => Err(<Error<Test>>::CodeNotFound)?,
|
||||
Entry::Occupied(mut entry) => entry.get_mut().refcount += 1,
|
||||
@@ -1457,8 +1455,7 @@ mod tests {
|
||||
|
||||
fn decrement_refcount(code_hash: CodeHash<Test>) {
|
||||
use std::collections::hash_map::Entry::Occupied;
|
||||
LOADER.with(|loader| {
|
||||
let mut loader = loader.borrow_mut();
|
||||
Loader::mutate(|loader| {
|
||||
let mut entry = match loader.map.entry(code_hash) {
|
||||
Occupied(e) => e,
|
||||
_ => panic!("code_hash does not exist"),
|
||||
@@ -1478,13 +1475,8 @@ mod tests {
|
||||
_schedule: &Schedule<Test>,
|
||||
_gas_meter: &mut GasMeter<Test>,
|
||||
) -> Result<Self, DispatchError> {
|
||||
LOADER.with(|loader| {
|
||||
loader
|
||||
.borrow_mut()
|
||||
.map
|
||||
.get(&code_hash)
|
||||
.cloned()
|
||||
.ok_or(Error::<Test>::CodeNotFound.into())
|
||||
Loader::mutate(|loader| {
|
||||
loader.map.get(&code_hash).cloned().ok_or(Error::<Test>::CodeNotFound.into())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1531,14 +1523,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
thread_local! {
|
||||
static TEST_DATA: RefCell<Vec<usize>> = RefCell::new(vec![0]);
|
||||
parameter_types! {
|
||||
static TestData: Vec<usize> = vec![0];
|
||||
}
|
||||
|
||||
let value = Default::default();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let exec_ch = MockLoader::insert(Call, |_ctx, _executable| {
|
||||
TEST_DATA.with(|data| data.borrow_mut().push(1));
|
||||
TestData::mutate(|data| data.push(1));
|
||||
exec_success()
|
||||
});
|
||||
|
||||
@@ -1562,7 +1554,7 @@ mod tests {
|
||||
);
|
||||
});
|
||||
|
||||
TEST_DATA.with(|data| assert_eq!(*data.borrow(), vec![0, 1]));
|
||||
assert_eq!(TestData::get(), vec![0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1841,16 +1833,15 @@ mod tests {
|
||||
fn max_depth() {
|
||||
// This test verifies that when we reach the maximal depth creation of an
|
||||
// yet another context fails.
|
||||
thread_local! {
|
||||
static REACHED_BOTTOM: RefCell<bool> = RefCell::new(false);
|
||||
parameter_types! {
|
||||
static ReachedBottom: bool = false;
|
||||
}
|
||||
let value = Default::default();
|
||||
let recurse_ch = MockLoader::insert(Call, |ctx, _| {
|
||||
// Try to call into yourself.
|
||||
let r = ctx.ext.call(Weight::zero(), BOB, 0, vec![], true);
|
||||
|
||||
REACHED_BOTTOM.with(|reached_bottom| {
|
||||
let mut reached_bottom = reached_bottom.borrow_mut();
|
||||
ReachedBottom::mutate(|reached_bottom| {
|
||||
if !*reached_bottom {
|
||||
// We are first time here, it means we just reached bottom.
|
||||
// Verify that we've got proper error and set `reached_bottom`.
|
||||
@@ -1891,15 +1882,14 @@ mod tests {
|
||||
let origin = ALICE;
|
||||
let dest = BOB;
|
||||
|
||||
thread_local! {
|
||||
static WITNESSED_CALLER_BOB: RefCell<Option<AccountIdOf<Test>>> = RefCell::new(None);
|
||||
static WITNESSED_CALLER_CHARLIE: RefCell<Option<AccountIdOf<Test>>> = RefCell::new(None);
|
||||
parameter_types! {
|
||||
static WitnessedCallerBob: Option<AccountIdOf<Test>> = None;
|
||||
static WitnessedCallerCharlie: Option<AccountIdOf<Test>> = None;
|
||||
}
|
||||
|
||||
let bob_ch = MockLoader::insert(Call, |ctx, _| {
|
||||
// Record the caller for bob.
|
||||
WITNESSED_CALLER_BOB
|
||||
.with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone()));
|
||||
WitnessedCallerBob::mutate(|caller| *caller = Some(ctx.ext.caller().clone()));
|
||||
|
||||
// Call into CHARLIE contract.
|
||||
assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_));
|
||||
@@ -1907,8 +1897,7 @@ mod tests {
|
||||
});
|
||||
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
|
||||
// Record the caller for charlie.
|
||||
WITNESSED_CALLER_CHARLIE
|
||||
.with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone()));
|
||||
WitnessedCallerCharlie::mutate(|caller| *caller = Some(ctx.ext.caller().clone()));
|
||||
exec_success()
|
||||
});
|
||||
|
||||
@@ -1932,8 +1921,8 @@ mod tests {
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
|
||||
WITNESSED_CALLER_BOB.with(|caller| assert_eq!(*caller.borrow(), Some(origin)));
|
||||
WITNESSED_CALLER_CHARLIE.with(|caller| assert_eq!(*caller.borrow(), Some(dest)));
|
||||
assert_eq!(WitnessedCallerBob::get(), Some(origin));
|
||||
assert_eq!(WitnessedCallerCharlie::get(), Some(dest));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -441,23 +441,23 @@ mod tests {
|
||||
exec::AccountIdOf,
|
||||
tests::{Test, ALICE, BOB, CHARLIE},
|
||||
};
|
||||
use frame_support::parameter_types;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::cell::RefCell;
|
||||
|
||||
type TestMeter = RawMeter<Test, TestExt, Root>;
|
||||
|
||||
thread_local! {
|
||||
static TEST_EXT: RefCell<TestExt> = RefCell::new(Default::default());
|
||||
parameter_types! {
|
||||
static TestExtTestValue: TestExt = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct LimitCheck {
|
||||
origin: AccountIdOf<Test>,
|
||||
limit: BalanceOf<Test>,
|
||||
min_leftover: BalanceOf<Test>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct Charge {
|
||||
origin: AccountIdOf<Test>,
|
||||
contract: AccountIdOf<Test>,
|
||||
@@ -465,8 +465,8 @@ mod tests {
|
||||
terminated: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
struct TestExt {
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TestExt {
|
||||
limit_checks: Vec<LimitCheck>,
|
||||
charges: Vec<Charge>,
|
||||
}
|
||||
@@ -485,12 +485,9 @@ mod tests {
|
||||
min_leftover: BalanceOf<Test>,
|
||||
) -> Result<BalanceOf<Test>, DispatchError> {
|
||||
let limit = limit.unwrap_or(42);
|
||||
TEST_EXT.with(|ext| {
|
||||
ext.borrow_mut().limit_checks.push(LimitCheck {
|
||||
origin: origin.clone(),
|
||||
limit,
|
||||
min_leftover,
|
||||
})
|
||||
TestExtTestValue::mutate(|ext| {
|
||||
ext.limit_checks
|
||||
.push(LimitCheck { origin: origin.clone(), limit, min_leftover })
|
||||
});
|
||||
Ok(limit)
|
||||
}
|
||||
@@ -501,8 +498,8 @@ mod tests {
|
||||
amount: &DepositOf<Test>,
|
||||
terminated: bool,
|
||||
) {
|
||||
TEST_EXT.with(|ext| {
|
||||
ext.borrow_mut().charges.push(Charge {
|
||||
TestExtTestValue::mutate(|ext| {
|
||||
ext.charges.push(Charge {
|
||||
origin: origin.clone(),
|
||||
contract: contract.clone(),
|
||||
amount: amount.clone(),
|
||||
@@ -513,7 +510,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn clear_ext() {
|
||||
TEST_EXT.with(|ext| ext.borrow_mut().clear())
|
||||
TestExtTestValue::mutate(|ext| ext.clear())
|
||||
}
|
||||
|
||||
fn new_info(deposit: BalanceOf<Test>) -> ContractInfo<Test> {
|
||||
@@ -533,15 +530,13 @@ mod tests {
|
||||
|
||||
TestMeter::new(&ALICE, Some(1_000), 0).unwrap();
|
||||
|
||||
TEST_EXT.with(|ext| {
|
||||
assert_eq!(
|
||||
*ext.borrow(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
TestExtTestValue::get(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -556,15 +551,13 @@ mod tests {
|
||||
nested0.charge(&Default::default()).unwrap();
|
||||
meter.absorb(nested0, &ALICE, &BOB, None);
|
||||
|
||||
TEST_EXT.with(|ext| {
|
||||
assert_eq!(
|
||||
*ext.borrow(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
TestExtTestValue::get(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -582,20 +575,18 @@ mod tests {
|
||||
.unwrap();
|
||||
meter.absorb(nested0, &ALICE, &BOB, None);
|
||||
|
||||
TEST_EXT.with(|ext| {
|
||||
assert_eq!(
|
||||
*ext.borrow(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
charges: vec![Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(<Test as Config>::Currency::minimum_balance() * 2),
|
||||
terminated: false,
|
||||
}]
|
||||
}
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
TestExtTestValue::get(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
charges: vec![Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(<Test as Config>::Currency::minimum_balance() * 2),
|
||||
terminated: false,
|
||||
}]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -638,34 +629,32 @@ mod tests {
|
||||
assert_eq!(nested1_info.storage_deposit, 40);
|
||||
assert_eq!(nested2_info.storage_deposit, min_balance);
|
||||
|
||||
TEST_EXT.with(|ext| {
|
||||
assert_eq!(
|
||||
*ext.borrow(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
charges: vec![
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(10),
|
||||
terminated: false
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(4),
|
||||
terminated: false
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(2),
|
||||
terminated: false
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
TestExtTestValue::get(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
charges: vec![
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(10),
|
||||
terminated: false
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(4),
|
||||
terminated: false
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(2),
|
||||
terminated: false
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -697,27 +686,25 @@ mod tests {
|
||||
meter.absorb(nested0, &ALICE, &BOB, None);
|
||||
drop(meter);
|
||||
|
||||
TEST_EXT.with(|ext| {
|
||||
assert_eq!(
|
||||
*ext.borrow(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
charges: vec![
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(400),
|
||||
terminated: true
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(12),
|
||||
terminated: false
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
TestExtTestValue::get(),
|
||||
TestExt {
|
||||
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
|
||||
charges: vec![
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: CHARLIE,
|
||||
amount: Deposit::Refund(400),
|
||||
terminated: true
|
||||
},
|
||||
Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(12),
|
||||
terminated: false
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ use sp_runtime::{
|
||||
traits::{BlakeTwo256, Convert, Hash, IdentityLookup},
|
||||
AccountId32,
|
||||
};
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate as pallet_contracts;
|
||||
|
||||
@@ -113,10 +113,11 @@ pub mod test_utils {
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TEST_EXTENSION: RefCell<TestExtension> = Default::default();
|
||||
parameter_types! {
|
||||
static TestExtensionTestValue: TestExtension = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestExtension {
|
||||
enabled: bool,
|
||||
last_seen_buffer: Vec<u8>,
|
||||
@@ -136,15 +137,15 @@ pub struct TempStorageExtension {
|
||||
|
||||
impl TestExtension {
|
||||
fn disable() {
|
||||
TEST_EXTENSION.with(|e| e.borrow_mut().enabled = false)
|
||||
TestExtensionTestValue::mutate(|e| e.enabled = false)
|
||||
}
|
||||
|
||||
fn last_seen_buffer() -> Vec<u8> {
|
||||
TEST_EXTENSION.with(|e| e.borrow().last_seen_buffer.clone())
|
||||
TestExtensionTestValue::get().last_seen_buffer.clone()
|
||||
}
|
||||
|
||||
fn last_seen_inputs() -> (u32, u32, u32, u32) {
|
||||
TEST_EXTENSION.with(|e| e.borrow().last_seen_inputs)
|
||||
TestExtensionTestValue::get().last_seen_inputs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,14 +168,13 @@ impl ChainExtension<Test> for TestExtension {
|
||||
let mut env = env.buf_in_buf_out();
|
||||
let input = env.read(8)?;
|
||||
env.write(&input, false, None)?;
|
||||
TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input);
|
||||
TestExtensionTestValue::mutate(|e| e.last_seen_buffer = input);
|
||||
Ok(RetVal::Converging(id))
|
||||
},
|
||||
0x8001 => {
|
||||
let env = env.only_in();
|
||||
TEST_EXTENSION.with(|e| {
|
||||
e.borrow_mut().last_seen_inputs =
|
||||
(env.val0(), env.val1(), env.val2(), env.val3())
|
||||
TestExtensionTestValue::mutate(|e| {
|
||||
e.last_seen_inputs = (env.val0(), env.val1(), env.val2(), env.val3())
|
||||
});
|
||||
Ok(RetVal::Converging(id))
|
||||
},
|
||||
@@ -192,7 +192,7 @@ impl ChainExtension<Test> for TestExtension {
|
||||
}
|
||||
|
||||
fn enabled() -> bool {
|
||||
TEST_EXTENSION.with(|e| e.borrow().enabled)
|
||||
TestExtensionTestValue::get().enabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ impl ChainExtension<Test> for RevertingExtension {
|
||||
}
|
||||
|
||||
fn enabled() -> bool {
|
||||
TEST_EXTENSION.with(|e| e.borrow().enabled)
|
||||
TestExtensionTestValue::get().enabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ impl ChainExtension<Test> for TempStorageExtension {
|
||||
}
|
||||
|
||||
fn enabled() -> bool {
|
||||
TEST_EXTENSION.with(|e| e.borrow().enabled)
|
||||
TestExtensionTestValue::get().enabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,19 +344,30 @@ impl Convert<Weight, BalanceOf<Self>> for Test {
|
||||
/// A filter whose filter function can be swapped at runtime.
|
||||
pub struct TestFilter;
|
||||
|
||||
thread_local! {
|
||||
static CALL_FILTER: RefCell<fn(&Call) -> bool> = RefCell::new(|_| true);
|
||||
#[derive(Clone)]
|
||||
pub struct Filters {
|
||||
filter: fn(&Call) -> bool,
|
||||
}
|
||||
|
||||
impl Default for Filters {
|
||||
fn default() -> Self {
|
||||
Filters { filter: (|_| true) }
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
static CallFilter: Filters = Default::default();
|
||||
}
|
||||
|
||||
impl TestFilter {
|
||||
pub fn set_filter(filter: fn(&Call) -> bool) {
|
||||
CALL_FILTER.with(|fltr| *fltr.borrow_mut() = filter);
|
||||
CallFilter::mutate(|fltr| fltr.filter = filter);
|
||||
}
|
||||
}
|
||||
|
||||
impl Contains<Call> for TestFilter {
|
||||
fn contains(call: &Call) -> bool {
|
||||
CALL_FILTER.with(|fltr| fltr.borrow()(call))
|
||||
(CallFilter::get().filter)(call)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user