mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
genesis mint working
This commit is contained in:
@@ -443,6 +443,7 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/tx/impl/SetRegularKey.cpp
|
||||
src/ripple/app/tx/impl/SetHook.cpp
|
||||
src/ripple/app/tx/impl/ClaimReward.cpp
|
||||
src/ripple/app/tx/impl/GenesisMint.cpp
|
||||
src/ripple/app/tx/impl/Import.cpp
|
||||
src/ripple/app/tx/impl/Invoke.cpp
|
||||
src/ripple/app/tx/impl/SetSignerList.cpp
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
all: reward govern
|
||||
all: reward govern mint
|
||||
reward:
|
||||
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../
|
||||
wasm-opt reward.wasm -o reward.wasm \
|
||||
@@ -109,3 +109,58 @@ govern:
|
||||
-Oz
|
||||
hook-cleaner govern.wasm
|
||||
guard_checker govern.wasm
|
||||
mint:
|
||||
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I../
|
||||
wasm-opt mint.wasm -o mint.wasm \
|
||||
--shrink-level=100000000 \
|
||||
--coalesce-locals-learning \
|
||||
--vacuum \
|
||||
--merge-blocks \
|
||||
--merge-locals \
|
||||
--flatten \
|
||||
--ignore-implicit-traps \
|
||||
-ffm \
|
||||
--const-hoisting \
|
||||
--code-folding \
|
||||
--code-pushing \
|
||||
--dae-optimizing \
|
||||
--dce \
|
||||
--simplify-globals-optimizing \
|
||||
--simplify-locals-nonesting \
|
||||
--reorder-locals \
|
||||
--rereloop \
|
||||
--precompute-propagate \
|
||||
--local-cse \
|
||||
--remove-unused-brs \
|
||||
--memory-packing \
|
||||
-c \
|
||||
--avoid-reinterprets \
|
||||
-Oz
|
||||
hook-cleaner mint.wasm
|
||||
wasm-opt mint.wasm -o mint.wasm \
|
||||
--shrink-level=100000000 \
|
||||
--coalesce-locals-learning \
|
||||
--vacuum \
|
||||
--merge-blocks \
|
||||
--merge-locals \
|
||||
--flatten \
|
||||
--ignore-implicit-traps \
|
||||
-ffm \
|
||||
--const-hoisting \
|
||||
--code-folding \
|
||||
--code-pushing \
|
||||
--dae-optimizing \
|
||||
--dce \
|
||||
--simplify-globals-optimizing \
|
||||
--simplify-locals-nonesting \
|
||||
--reorder-locals \
|
||||
--rereloop \
|
||||
--precompute-propagate \
|
||||
--local-cse \
|
||||
--remove-unused-brs \
|
||||
--memory-packing \
|
||||
-c \
|
||||
--avoid-reinterprets \
|
||||
-Oz
|
||||
hook-cleaner mint.wasm
|
||||
guard_checker mint.wasm
|
||||
|
||||
114
hook/mint.c
Normal file
114
hook/mint.c
Normal file
@@ -0,0 +1,114 @@
|
||||
// This hook just tests GenesisMint transactor, it is not for production use
|
||||
|
||||
#include "hookapi.h"
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback(SBUF("Reward: Assertion failure."),__LINE__);
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
uint8_t txn_mint[13844] =
|
||||
{
|
||||
/* size,upto */
|
||||
/* 3, 0 */ 0x12U, 0x00U, 0x60U, /* tt = GenesisMint */
|
||||
/* 5, 3 */ 0x22U, 0x80U, 0x00U, 0x00U, 0x00U, /* flags = tfCanonical */
|
||||
/* 5, 8 */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, /* sequence = 0 */
|
||||
/* 6, 13 */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, /* first ledger seq */
|
||||
/* 6, 19 */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, /* last ledger seq */
|
||||
/* 9, 25 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */
|
||||
/* 35, 34 */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* pubkey */
|
||||
/* 22, 69 */ 0x81U, 0x14U, 0xB5U,0xF7U,0x62U,0x79U,0x8AU,0x53U,0xD5U,0x43U,0xA0U,0x14U,
|
||||
0xCAU,0xF8U,0xB2U,0x97U,0xCFU,0xF8U,0xF2U,0xF9U,0x37U,0xE8U, /* src acc */
|
||||
|
||||
/* 116, 91 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* emit detail */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
|
||||
/* 207, ... */ 0xF0U, 0x60U, /* gen mints arr */
|
||||
/* 34 bytes per entries + 1 tail byte
|
||||
E060
|
||||
61
|
||||
4111111111111111 // amount
|
||||
8114
|
||||
1234567891234567891234567891234567891234 // account
|
||||
E1
|
||||
... repeat
|
||||
F1 // tail byte
|
||||
*
|
||||
* */
|
||||
};
|
||||
|
||||
#define BE_DROPS(drops)\
|
||||
{\
|
||||
uint64_t drops_tmp = drops;\
|
||||
uint8_t* b = (uint8_t*)&drops;\
|
||||
*b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\
|
||||
*b++ = (drops_tmp >> 48) & 0xFFU;\
|
||||
*b++ = (drops_tmp >> 40) & 0xFFU;\
|
||||
*b++ = (drops_tmp >> 32) & 0xFFU;\
|
||||
*b++ = (drops_tmp >> 24) & 0xFFU;\
|
||||
*b++ = (drops_tmp >> 16) & 0xFFU;\
|
||||
*b++ = (drops_tmp >> 8) & 0xFFU;\
|
||||
*b++ = (drops_tmp >> 0) & 0xFFU;\
|
||||
}
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
etxn_reserve(1);
|
||||
_g(1,1);
|
||||
|
||||
|
||||
// emit the txn
|
||||
uint64_t drops = 200;
|
||||
BE_DROPS(drops);
|
||||
|
||||
|
||||
uint8_t* upto = txn_mint + 209U;
|
||||
|
||||
*upto++ = 0xE0U; // obj start
|
||||
*upto++ = 0x60U;
|
||||
|
||||
*upto++ = 0x61U; // amt
|
||||
*((uint64_t*)upto) = drops;
|
||||
upto += 8;
|
||||
|
||||
*upto++ = 0x83U; // acc
|
||||
*upto++ = 0x14U;
|
||||
otxn_field(upto, 20, sfDestination);
|
||||
upto += 20;
|
||||
*upto++ = 0xE1U; // obj end
|
||||
*upto++ = 0xF1U; // array end
|
||||
|
||||
etxn_details(txn_mint + 91, 116);
|
||||
int64_t fee = etxn_fee_base(txn_mint, upto - txn_mint);
|
||||
BE_DROPS(fee);
|
||||
|
||||
*((uint64_t*)(txn_mint + 26)) = fee;
|
||||
|
||||
int64_t seq = ledger_seq() + 1;
|
||||
txn_mint[15] = (seq >> 24U) & 0xFFU;
|
||||
txn_mint[16] = (seq >> 16U) & 0xFFU;
|
||||
txn_mint[17] = (seq >> 8U) & 0xFFU;
|
||||
txn_mint[18] = seq & 0xFFU;
|
||||
|
||||
seq += 4;
|
||||
txn_mint[21] = (seq >> 24U) & 0xFFU;
|
||||
txn_mint[22] = (seq >> 16U) & 0xFFU;
|
||||
txn_mint[23] = (seq >> 8U) & 0xFFU;
|
||||
txn_mint[24] = seq & 0xFFU;
|
||||
|
||||
|
||||
trace(SBUF("emit:"), txn_mint, upto-txn_mint, 1);
|
||||
|
||||
uint8_t emithash[32];
|
||||
int64_t emit_result = emit(SBUF(emithash), txn_mint, upto - txn_mint);
|
||||
|
||||
if (DEBUG)
|
||||
TRACEVAR(emit_result);
|
||||
|
||||
if (emit_result < 0)
|
||||
rollback(SBUF("MintTest: Emit failed."), __LINE__);
|
||||
|
||||
|
||||
accept(SBUF("MintTest: Emitted txn successfully."), __LINE__);
|
||||
}
|
||||
187
src/ripple/app/tx/impl/GenesisMint.cpp
Normal file
187
src/ripple/app/tx/impl/GenesisMint.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/tx/impl/GenesisMint.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
TxConsequences
|
||||
GenesisMint::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
// RH TODO: review this
|
||||
return TxConsequences{ctx.tx, TxConsequences::normal};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
GenesisMint::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto& tx = ctx.tx;
|
||||
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
static auto const genesisAccountId = calcAccountID(
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase"))
|
||||
.first);
|
||||
|
||||
if (id != genesisAccountId || !tx.isFieldPresent(sfEmitDetails))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: can only be used by the genesis account in an emitted transaction.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const& dests = tx.getFieldArray(sfGenesisMints);
|
||||
if (dests.empty())
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: destinations array empty.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (dests.size() > 512)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: destinations array exceeds 512 entries.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
std::unordered_set<AccountID> alreadySeen;
|
||||
for (auto const& dest: dests)
|
||||
{
|
||||
if (dest.getFName() != sfGenesisMint)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: destinations array contained an invalid entry.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const amt = dest.getFieldAmount(sfAmount);
|
||||
if (!isXRP(amt))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: only native amounts can be minted.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (amt <= beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: only positive amounts can be minted.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const accid = dest.getAccountID(sfDestination);
|
||||
|
||||
if (accid == noAccount() || accid == xrpAccount())
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: destinations includes disallowed account zero or one.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (alreadySeen.find(accid) != alreadySeen.end())
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "GenesisMint: duplicate in destinations.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
alreadySeen.emplace(accid);
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
GenesisMint::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureHooks))
|
||||
return temDISABLED;
|
||||
|
||||
if (!ctx.view.rules().enabled(featureXahauGenesis))
|
||||
return temDISABLED;
|
||||
|
||||
// RH UPTO:
|
||||
// check that printing won't exceed 200% of the total coins on the ledger
|
||||
// this will act as a hard cap against malfunction
|
||||
// modify the invariant checkers
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
GenesisMint::doApply()
|
||||
{
|
||||
auto const& dests = ctx_.tx.getFieldArray(sfGenesisMints);
|
||||
|
||||
for (auto const& dest: dests)
|
||||
{
|
||||
auto const amt = dest.getFieldAmount(sfAmount);
|
||||
auto const id = dest.getAccountID(sfDestination);
|
||||
auto const k = keylet::account(id);
|
||||
auto sle = view().peek(k);
|
||||
if (!sle)
|
||||
{
|
||||
// Create the account.
|
||||
std::uint32_t const seqno{
|
||||
view().rules().enabled(featureDeletableAccounts) ? view().seq()
|
||||
: 1};
|
||||
sle = std::make_shared<SLE>(k);
|
||||
sle->setAccountID(sfAccount, id);
|
||||
|
||||
sle->setFieldU32(sfSequence, seqno);
|
||||
sle->setFieldAmount(sfBalance, amt);
|
||||
|
||||
view().insert(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Credit the account
|
||||
STAmount startBal = sle->getFieldAmount(sfBalance);
|
||||
STAmount finalBal = startBal + amt;
|
||||
if (finalBal > startBal)
|
||||
{
|
||||
sle->setFieldAmount(sfBalance, finalBal);
|
||||
view().update(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(ctx_.journal.warn())
|
||||
<< "GenesisMint: cannot credit " << dest << " due to balance overflow";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
GenesisMint::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
return Transactor::calculateBaseFee(view, tx);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
57
src/ripple/app/tx/impl/GenesisMint.h
Normal file
57
src/ripple/app/tx/impl/GenesisMint.h
Normal file
@@ -0,0 +1,57 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_GENESISMINT_H_INCLUDED
|
||||
#define RIPPLE_TX_GENESISMINT_H_INCLUDED
|
||||
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class GenesisMint : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit GenesisMint(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <ripple/app/tx/impl/Import.h>
|
||||
#include <ripple/app/tx/impl/Invoke.h>
|
||||
#include <ripple/app/tx/impl/URIToken.h>
|
||||
#include <ripple/app/tx/impl/GenesisMint.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -156,6 +157,8 @@ invoke_preflight(PreflightContext const& ctx)
|
||||
return invoke_preflight_helper<NFTokenAcceptOffer>(ctx);
|
||||
case ttCLAIM_REWARD:
|
||||
return invoke_preflight_helper<ClaimReward>(ctx);
|
||||
case ttGENESIS_MINT:
|
||||
return invoke_preflight_helper<GenesisMint>(ctx);
|
||||
case ttIMPORT:
|
||||
return invoke_preflight_helper<Import>(ctx);
|
||||
case ttINVOKE:
|
||||
@@ -275,6 +278,8 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
return invoke_preclaim<NFTokenAcceptOffer>(ctx);
|
||||
case ttCLAIM_REWARD:
|
||||
return invoke_preclaim<ClaimReward>(ctx);
|
||||
case ttGENESIS_MINT:
|
||||
return invoke_preclaim<GenesisMint>(ctx);
|
||||
case ttIMPORT:
|
||||
return invoke_preclaim<Import>(ctx);
|
||||
case ttINVOKE:
|
||||
@@ -353,6 +358,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return NFTokenAcceptOffer::calculateBaseFee(view, tx);
|
||||
case ttCLAIM_REWARD:
|
||||
return ClaimReward::calculateBaseFee(view, tx);
|
||||
case ttGENESIS_MINT:
|
||||
return GenesisMint::calculateBaseFee(view, tx);
|
||||
case ttIMPORT:
|
||||
return Import::calculateBaseFee(view, tx);
|
||||
case ttINVOKE:
|
||||
@@ -525,6 +532,10 @@ invoke_apply(ApplyContext& ctx)
|
||||
ClaimReward p(ctx);
|
||||
return p();
|
||||
}
|
||||
case ttGENESIS_MINT: {
|
||||
GenesisMint p(ctx);
|
||||
return p();
|
||||
}
|
||||
case ttIMPORT: {
|
||||
Import p(ctx);
|
||||
return p();
|
||||
|
||||
@@ -336,7 +336,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT,
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 25);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 96);
|
||||
|
||||
// array of objects
|
||||
// ARRAY/1 is reserved for end of array
|
||||
@@ -357,7 +357,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY,
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 21);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 96);
|
||||
|
||||
// clang-format on
|
||||
|
||||
|
||||
Reference in New Issue
Block a user