mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-12 07:28:08 -05:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f4df55d12 | ||
|
|
f21d4adbfd | ||
|
|
145edfccb9 |
@@ -5,7 +5,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var tag = "v4.3.4"
|
||||
var tag = "v4.3.5"
|
||||
|
||||
var commit = func() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "^v4.9.3",
|
||||
"@openzeppelin/contracts-upgradeable": "^v4.9.2"
|
||||
"@openzeppelin/contracts-upgradeable": "^v4.9.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts}": "npx eslint --cache --fix",
|
||||
|
||||
@@ -98,7 +98,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
}
|
||||
|
||||
address owner = vm.addr(L2_DEPLOYER_PRIVATE_KEY);
|
||||
L2TxFeeVault feeVault = new L2TxFeeVault(address(owner), L1_TX_FEE_RECIPIENT_ADDR);
|
||||
L2TxFeeVault feeVault = new L2TxFeeVault(address(owner), L1_TX_FEE_RECIPIENT_ADDR, 10 ether);
|
||||
|
||||
logAddress("L2_TX_FEE_VAULT_ADDR", address(feeVault));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,164 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Optimism
|
||||
// Copyright (c) 2022 Scroll
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
pragma solidity =0.8.16;
|
||||
|
||||
import {FeeVault} from "../../libraries/FeeVault.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {OwnableBase} from "../../libraries/common/OwnableBase.sol";
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
// solhint-disable reason-string
|
||||
|
||||
/// @title L2TxFeeVault
|
||||
/// @notice The `L2TxFeeVault` contract collects all L2 transaction fees and allows withdrawing these fees to a predefined L1 address.
|
||||
/// The minimum withdrawal amount is 10 ether.
|
||||
contract L2TxFeeVault is FeeVault {
|
||||
/// @param _owner The owner of the contract.
|
||||
/// @param _recipient The fee recipient address on L1.
|
||||
constructor(address _owner, address _recipient) FeeVault(_owner, _recipient, 10 ether) {}
|
||||
/// @notice The L2TxFeeVault contract contains the basic logic for the various different vault contracts
|
||||
/// used to hold fee revenue generated by the L2 system.
|
||||
contract L2TxFeeVault is OwnableBase {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emits each time that a withdrawal occurs.
|
||||
///
|
||||
/// @param value Amount that was withdrawn (in wei).
|
||||
/// @param to Address that the funds were sent to.
|
||||
/// @param from Address that triggered the withdrawal.
|
||||
event Withdrawal(uint256 value, address to, address from);
|
||||
|
||||
/// @notice Emits each time the owner updates the address of `messenger`.
|
||||
/// @param oldMessenger The address of old messenger.
|
||||
/// @param newMessenger The address of new messenger.
|
||||
event UpdateMessenger(address indexed oldMessenger, address indexed newMessenger);
|
||||
|
||||
/// @notice Emits each time the owner updates the address of `recipient`.
|
||||
/// @param oldRecipient The address of old recipient.
|
||||
/// @param newRecipient The address of new recipient.
|
||||
event UpdateRecipient(address indexed oldRecipient, address indexed newRecipient);
|
||||
|
||||
/// @notice Emits each time the owner updates the value of `minWithdrawAmount`.
|
||||
/// @param oldMinWithdrawAmount The value of old `minWithdrawAmount`.
|
||||
/// @param newMinWithdrawAmount The value of new `minWithdrawAmount`.
|
||||
event UpdateMinWithdrawAmount(uint256 oldMinWithdrawAmount, uint256 newMinWithdrawAmount);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Minimum balance before a withdrawal can be triggered.
|
||||
uint256 public minWithdrawAmount;
|
||||
|
||||
/// @notice Scroll L2 messenger address.
|
||||
address public messenger;
|
||||
|
||||
/// @notice Wallet that will receive the fees on L1.
|
||||
address public recipient;
|
||||
|
||||
/// @notice Total amount of wei processed by the contract.
|
||||
uint256 public totalProcessed;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @param _owner The owner of the contract.
|
||||
/// @param _recipient Wallet that will receive the fees on L1.
|
||||
/// @param _minWithdrawalAmount Minimum balance before a withdrawal can be triggered.
|
||||
constructor(
|
||||
address _owner,
|
||||
address _recipient,
|
||||
uint256 _minWithdrawalAmount
|
||||
) {
|
||||
_transferOwnership(_owner);
|
||||
|
||||
minWithdrawAmount = _minWithdrawalAmount;
|
||||
recipient = _recipient;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Allow the contract to receive ETH.
|
||||
receive() external payable {}
|
||||
|
||||
/// @notice Triggers a withdrawal of funds to the L1 fee wallet.
|
||||
/// @param _value The amount of ETH to withdraw.
|
||||
function withdraw(uint256 _value) public {
|
||||
require(
|
||||
_value >= minWithdrawAmount,
|
||||
"FeeVault: withdrawal amount must be greater than minimum withdrawal amount"
|
||||
);
|
||||
|
||||
unchecked {
|
||||
totalProcessed += _value;
|
||||
}
|
||||
|
||||
emit Withdrawal(_value, recipient, msg.sender);
|
||||
|
||||
// no fee provided
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: _value}(
|
||||
recipient,
|
||||
_value,
|
||||
bytes(""), // no message (simple eth transfer)
|
||||
0 // _gasLimit can be zero for fee vault.
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Triggers a withdrawal of all available funds to the L1 fee wallet.
|
||||
function withdraw() external {
|
||||
uint256 value = address(this).balance;
|
||||
withdraw(value);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the address of messenger.
|
||||
/// @param _newMessenger The address of messenger to update.
|
||||
function updateMessenger(address _newMessenger) external onlyOwner {
|
||||
address _oldMessenger = messenger;
|
||||
messenger = _newMessenger;
|
||||
|
||||
emit UpdateMessenger(_oldMessenger, _newMessenger);
|
||||
}
|
||||
|
||||
/// @notice Update the address of recipient.
|
||||
/// @param _newRecipient The address of recipient to update.
|
||||
function updateRecipient(address _newRecipient) external onlyOwner {
|
||||
address _oldRecipient = recipient;
|
||||
recipient = _newRecipient;
|
||||
|
||||
emit UpdateRecipient(_oldRecipient, _newRecipient);
|
||||
}
|
||||
|
||||
/// @notice Update the minimum withdraw amount.
|
||||
/// @param _newMinWithdrawAmount The minimum withdraw amount to update.
|
||||
function updateMinWithdrawAmount(uint256 _newMinWithdrawAmount) external onlyOwner {
|
||||
uint256 _oldMinWithdrawAmount = minWithdrawAmount;
|
||||
minWithdrawAmount = _newMinWithdrawAmount;
|
||||
|
||||
emit UpdateMinWithdrawAmount(_oldMinWithdrawAmount, _newMinWithdrawAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Optimism
|
||||
// Copyright (c) 2022 Scroll
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
pragma solidity ^0.8.16;
|
||||
|
||||
import {IL2ScrollMessenger} from "../L2/IL2ScrollMessenger.sol";
|
||||
import {OwnableBase} from "./common/OwnableBase.sol";
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
// solhint-disable reason-string
|
||||
|
||||
/// @title FeeVault
|
||||
/// @notice The FeeVault contract contains the basic logic for the various different vault contracts
|
||||
/// used to hold fee revenue generated by the L2 system.
|
||||
abstract contract FeeVault is OwnableBase {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emits each time that a withdrawal occurs.
|
||||
///
|
||||
/// @param value Amount that was withdrawn (in wei).
|
||||
/// @param to Address that the funds were sent to.
|
||||
/// @param from Address that triggered the withdrawal.
|
||||
event Withdrawal(uint256 value, address to, address from);
|
||||
|
||||
/// @notice Emits each time the owner updates the address of `messenger`.
|
||||
/// @param oldMessenger The address of old messenger.
|
||||
/// @param newMessenger The address of new messenger.
|
||||
event UpdateMessenger(address indexed oldMessenger, address indexed newMessenger);
|
||||
|
||||
/// @notice Emits each time the owner updates the address of `recipient`.
|
||||
/// @param oldRecipient The address of old recipient.
|
||||
/// @param newRecipient The address of new recipient.
|
||||
event UpdateRecipient(address indexed oldRecipient, address indexed newRecipient);
|
||||
|
||||
/// @notice Emits each time the owner updates the value of `minWithdrawAmount`.
|
||||
/// @param oldMinWithdrawAmount The value of old `minWithdrawAmount`.
|
||||
/// @param newMinWithdrawAmount The value of new `minWithdrawAmount`.
|
||||
event UpdateMinWithdrawAmount(uint256 oldMinWithdrawAmount, uint256 newMinWithdrawAmount);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Minimum balance before a withdrawal can be triggered.
|
||||
uint256 public minWithdrawAmount;
|
||||
|
||||
/// @notice Scroll L2 messenger address.
|
||||
address public messenger;
|
||||
|
||||
/// @notice Wallet that will receive the fees on L1.
|
||||
address public recipient;
|
||||
|
||||
/// @notice Total amount of wei processed by the contract.
|
||||
uint256 public totalProcessed;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @param _owner The owner of the contract.
|
||||
/// @param _recipient Wallet that will receive the fees on L1.
|
||||
/// @param _minWithdrawalAmount Minimum balance before a withdrawal can be triggered.
|
||||
constructor(
|
||||
address _owner,
|
||||
address _recipient,
|
||||
uint256 _minWithdrawalAmount
|
||||
) {
|
||||
_transferOwnership(_owner);
|
||||
|
||||
minWithdrawAmount = _minWithdrawalAmount;
|
||||
recipient = _recipient;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Allow the contract to receive ETH.
|
||||
receive() external payable {}
|
||||
|
||||
/// @notice Triggers a withdrawal of funds to the L1 fee wallet.
|
||||
/// @param _value The amount of ETH to withdraw.
|
||||
function withdraw(uint256 _value) public {
|
||||
require(
|
||||
_value >= minWithdrawAmount,
|
||||
"FeeVault: withdrawal amount must be greater than minimum withdrawal amount"
|
||||
);
|
||||
|
||||
unchecked {
|
||||
totalProcessed += _value;
|
||||
}
|
||||
|
||||
emit Withdrawal(_value, recipient, msg.sender);
|
||||
|
||||
// no fee provided
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: _value}(
|
||||
recipient,
|
||||
_value,
|
||||
bytes(""), // no message (simple eth transfer)
|
||||
0 // _gasLimit can be zero for fee vault.
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Triggers a withdrawal of all available funds to the L1 fee wallet.
|
||||
function withdraw() external {
|
||||
uint256 value = address(this).balance;
|
||||
withdraw(value);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the address of messenger.
|
||||
/// @param _newMessenger The address of messenger to update.
|
||||
function updateMessenger(address _newMessenger) external onlyOwner {
|
||||
address _oldMessenger = messenger;
|
||||
messenger = _newMessenger;
|
||||
|
||||
emit UpdateMessenger(_oldMessenger, _newMessenger);
|
||||
}
|
||||
|
||||
/// @notice Update the address of recipient.
|
||||
/// @param _newRecipient The address of recipient to update.
|
||||
function updateRecipient(address _newRecipient) external onlyOwner {
|
||||
address _oldRecipient = recipient;
|
||||
recipient = _newRecipient;
|
||||
|
||||
emit UpdateRecipient(_oldRecipient, _newRecipient);
|
||||
}
|
||||
|
||||
/// @notice Update the minimum withdraw amount.
|
||||
/// @param _newMinWithdrawAmount The minimum withdraw amount to update.
|
||||
function updateMinWithdrawAmount(uint256 _newMinWithdrawAmount) external onlyOwner {
|
||||
uint256 _oldMinWithdrawAmount = minWithdrawAmount;
|
||||
minWithdrawAmount = _newMinWithdrawAmount;
|
||||
|
||||
emit UpdateMinWithdrawAmount(_oldMinWithdrawAmount, _newMinWithdrawAmount);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ contract L2TxFeeVaultTest is DSTestPlus {
|
||||
|
||||
function setUp() public {
|
||||
messenger = new MockScrollMessenger();
|
||||
vault = new L2TxFeeVault(address(this), address(1));
|
||||
vault = new L2TxFeeVault(address(this), address(1), 10 ether);
|
||||
vault.updateMessenger(address(messenger));
|
||||
}
|
||||
|
||||
|
||||
@@ -652,10 +652,10 @@
|
||||
"@types/sinon-chai" "^3.2.3"
|
||||
"@types/web3" "1.0.19"
|
||||
|
||||
"@openzeppelin/contracts-upgradeable@^v4.9.2":
|
||||
version "4.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.2.tgz#a817c75688f8daede420052fbcb34e52482e769e"
|
||||
integrity sha512-siviV3PZV/fHfPaoIC51rf1Jb6iElkYWnNYZ0leO23/ukXuvOyoC/ahy8jqiV7g+++9Nuo3n/rk5ajSN/+d/Sg==
|
||||
"@openzeppelin/contracts-upgradeable@^v4.9.3":
|
||||
version "4.9.3"
|
||||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.3.tgz#ff17a80fb945f5102571f8efecb5ce5915cc4811"
|
||||
integrity sha512-jjaHAVRMrE4UuZNfDwjlLGDxTHWIOwTJS2ldnc278a0gevfXfPr8hxKEVBGFBE96kl2G3VHDZhUimw/+G3TG2A==
|
||||
|
||||
"@openzeppelin/contracts@^v4.9.3":
|
||||
version "4.9.3"
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"gas_price_diff": 50000
|
||||
},
|
||||
"chain_monitor": {
|
||||
"enabled": false,
|
||||
"timeout": 3,
|
||||
"try_times": 5,
|
||||
"base_url": "http://localhost:8750"
|
||||
|
||||
@@ -39,6 +39,7 @@ type SenderConfig struct {
|
||||
|
||||
// ChainMonitor this config is used to get batch status from chain_monitor API.
|
||||
type ChainMonitor struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
TimeOut int `json:"timeout"`
|
||||
TryTimes int `json:"try_times"`
|
||||
BaseURL string `json:"base_url"`
|
||||
@@ -56,7 +57,7 @@ type RelayerConfig struct {
|
||||
// gas oracle config
|
||||
GasOracleConfig *GasOracleConfig `json:"gas_oracle_config"`
|
||||
// ChainMonitor config of monitoring service
|
||||
ChainMonitor *ChainMonitor `json:"chain_monitor,omitempty"`
|
||||
ChainMonitor *ChainMonitor `json:"chain_monitor"`
|
||||
// L1CommitGasLimitMultiplier multiplier for fallback gas limit in commitBatch txs
|
||||
L1CommitGasLimitMultiplier float64 `json:"l1_commit_gas_limit_multiplier,omitempty"`
|
||||
// The private key of the relayer
|
||||
|
||||
@@ -98,11 +98,6 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.
|
||||
gasPriceDiff = defaultGasPriceDiff
|
||||
}
|
||||
|
||||
// chain_monitor client
|
||||
chainMonitorClient := resty.New()
|
||||
chainMonitorClient.SetRetryCount(cfg.ChainMonitor.TryTimes)
|
||||
chainMonitorClient.SetTimeout(time.Duration(cfg.ChainMonitor.TimeOut) * time.Second)
|
||||
|
||||
layer2Relayer := &Layer2Relayer{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
@@ -126,7 +121,13 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.
|
||||
cfg: cfg,
|
||||
processingCommitment: sync.Map{},
|
||||
processingFinalization: sync.Map{},
|
||||
chainMonitorClient: chainMonitorClient,
|
||||
}
|
||||
|
||||
// chain_monitor client
|
||||
if cfg.ChainMonitor.Enabled {
|
||||
layer2Relayer.chainMonitorClient = resty.New()
|
||||
layer2Relayer.chainMonitorClient.SetRetryCount(cfg.ChainMonitor.TryTimes)
|
||||
layer2Relayer.chainMonitorClient.SetTimeout(time.Duration(cfg.ChainMonitor.TimeOut) * time.Second)
|
||||
}
|
||||
|
||||
// Initialize genesis before we do anything else
|
||||
@@ -433,17 +434,20 @@ func (r *Layer2Relayer) ProcessCommittedBatches() {
|
||||
r.metrics.rollupL2RelayerProcessCommittedBatchesFinalizedTotal.Inc()
|
||||
|
||||
// Check batch status before send `finalizeBatchWithProof` tx.
|
||||
//batchStatus, err := r.getBatchStatusByIndex(batch.Index)
|
||||
//if err != nil {
|
||||
// r.metrics.rollupL2ChainMonitorLatestFailedCall.Inc()
|
||||
// log.Warn("failed to get batch status, please check chain_monitor api server", "batch_index", batch.Index, "err", err)
|
||||
// return
|
||||
//}
|
||||
//if !batchStatus {
|
||||
// r.metrics.rollupL2ChainMonitorLatestFailedBatchStatus.Inc()
|
||||
// log.Error("the batch status is not right, stop finalize batch and check the reason", "batch_index", batch.Index)
|
||||
// return
|
||||
//}
|
||||
if r.cfg.ChainMonitor.Enabled {
|
||||
var batchStatus bool
|
||||
batchStatus, err = r.getBatchStatusByIndex(batch.Index)
|
||||
if err != nil {
|
||||
r.metrics.rollupL2ChainMonitorLatestFailedCall.Inc()
|
||||
log.Warn("failed to get batch status, please check chain_monitor api server", "batch_index", batch.Index, "err", err)
|
||||
return
|
||||
}
|
||||
if !batchStatus {
|
||||
r.metrics.rollupL2ChainMonitorLatestFailedBatchStatus.Inc()
|
||||
log.Error("the batch status is not right, stop finalize batch and check the reason", "batch_index", batch.Index)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var parentBatchStateRoot string
|
||||
if batch.Index > 0 {
|
||||
|
||||
@@ -392,6 +392,7 @@ func testGetBatchStatusByIndex(t *testing.T) {
|
||||
db := setupL2RelayerDB(t)
|
||||
defer database.CloseDB(db)
|
||||
|
||||
cfg.L2Config.RelayerConfig.ChainMonitor.Enabled = true
|
||||
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig, false, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, relayer)
|
||||
|
||||
Reference in New Issue
Block a user