fix: resolve all compilation issues across transport and lifecycle packages
- Fixed duplicate type declarations in transport package - Removed unused variables in lifecycle and dependency injection - Fixed big.Int arithmetic operations in uniswap contracts - Added missing methods to MetricsCollector (IncrementCounter, RecordLatency, etc.) - Fixed jitter calculation in TCP transport retry logic - Updated ComponentHealth field access to use transport type - Ensured all core packages build successfully All major compilation errors resolved: ✅ Transport package builds clean ✅ Lifecycle package builds clean ✅ Main MEV bot application builds clean ✅ Fixed method signature mismatches ✅ Resolved type conflicts and duplications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
471
pkg/arbitrum/new_parsers_test.go
Normal file
471
pkg/arbitrum/new_parsers_test.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package arbitrum
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Mock RPC client for testing
|
||||
type mockRPCClient struct {
|
||||
responses map[string]interface{}
|
||||
}
|
||||
|
||||
func (m *mockRPCClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||
// Mock successful responses based on method
|
||||
switch method {
|
||||
case "eth_call":
|
||||
// Mock token0/token1/fee responses
|
||||
call := args[0].(map[string]interface{})
|
||||
data := call["data"].(string)
|
||||
|
||||
// Mock responses for different function calls
|
||||
switch {
|
||||
case len(data) >= 10 && data[2:10] == "0d0e30db": // token0()
|
||||
*result.(*string) = "0x000000000000000000000000A0b86a33E6441f43E2e4A96439abFA2A69067ACD" // Mock token address
|
||||
case len(data) >= 10 && data[2:10] == "d21220a7": // token1()
|
||||
*result.(*string) = "0x000000000000000000000000af88d065e77c8cC2239327C5EDb3A432268e5831" // Mock token address
|
||||
case len(data) >= 10 && data[2:10] == "ddca3f43": // fee()
|
||||
*result.(*string) = "0x0000000000000000000000000000000000000000000000000000000000000bb8" // 3000 (0.3%)
|
||||
case len(data) >= 10 && data[2:10] == "fc0e74d1": // getTokenX()
|
||||
*result.(*string) = "0x000000000000000000000000A0b86a33E6441f43E2e4A96439abFA2A69067ACD" // Mock token address
|
||||
case len(data) >= 10 && data[2:10] == "8cc8b9a9": // getTokenY()
|
||||
*result.(*string) = "0x000000000000000000000000af88d065e77c8cC2239327C5EDb3A432268e5831" // Mock token address
|
||||
case len(data) >= 10 && data[2:10] == "69fe0e2d": // getBinStep()
|
||||
*result.(*string) = "0x0000000000000000000000000000000000000000000000000000000000000019" // 25 (bin step)
|
||||
default:
|
||||
*result.(*string) = "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
case "eth_getLogs":
|
||||
// Mock empty logs for pool discovery
|
||||
*result.(*[]interface{}) = []interface{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createMockLogger() *logger.Logger {
|
||||
return logger.New("debug", "text", "")
|
||||
}
|
||||
|
||||
func createMockRPCClient() *rpc.Client {
|
||||
// Create a mock that satisfies the interface
|
||||
return &rpc.Client{}
|
||||
}
|
||||
|
||||
// Test CamelotV3Parser
|
||||
func TestCamelotV3Parser_New(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
|
||||
parser := NewCamelotV3Parser(client, logger)
|
||||
require.NotNil(t, parser)
|
||||
|
||||
camelotParser, ok := parser.(*CamelotV3Parser)
|
||||
require.True(t, ok, "Parser should be CamelotV3Parser type")
|
||||
assert.Equal(t, ProtocolCamelotV3, camelotParser.protocol)
|
||||
}
|
||||
|
||||
func TestCamelotV3Parser_GetSupportedContractTypes(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser)
|
||||
|
||||
types := parser.GetSupportedContractTypes()
|
||||
assert.Contains(t, types, ContractTypeFactory)
|
||||
assert.Contains(t, types, ContractTypeRouter)
|
||||
assert.Contains(t, types, ContractTypePool)
|
||||
}
|
||||
|
||||
func TestCamelotV3Parser_GetSupportedEventTypes(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser)
|
||||
|
||||
events := parser.GetSupportedEventTypes()
|
||||
assert.Contains(t, events, EventTypeSwap)
|
||||
assert.Contains(t, events, EventTypeLiquidityAdd)
|
||||
assert.Contains(t, events, EventTypeLiquidityRemove)
|
||||
assert.Contains(t, events, EventTypePoolCreated)
|
||||
assert.Contains(t, events, EventTypePositionUpdate)
|
||||
}
|
||||
|
||||
func TestCamelotV3Parser_IsKnownContract(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser)
|
||||
|
||||
// Test known contract (factory)
|
||||
factoryAddr := common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B")
|
||||
assert.True(t, parser.IsKnownContract(factoryAddr))
|
||||
|
||||
// Test unknown contract
|
||||
unknownAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
assert.False(t, parser.IsKnownContract(unknownAddr))
|
||||
}
|
||||
|
||||
func TestCamelotV3Parser_ParseLog(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser)
|
||||
|
||||
// Create mock swap log
|
||||
factoryAddr := common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B")
|
||||
_ = parser.eventSigs // Reference to avoid unused variable warning
|
||||
|
||||
log := &types.Log{
|
||||
Address: factoryAddr,
|
||||
Topics: []common.Hash{
|
||||
common.HexToHash("0xe14ced199d67634c498b12b8ffc4244e2be5b5f2b3b7b0db5c35b2c73b89b3b8"), // Swap event topic
|
||||
common.HexToHash("0x000000000000000000000000742d35Cc6AaB8f5d6649c8C4F7C6b2d1234567890"), // sender
|
||||
common.HexToHash("0x000000000000000000000000742d35Cc6AaB8f5d6649c8C4F7C6b2d0987654321"), // recipient
|
||||
},
|
||||
Data: make([]byte, 160), // 5 * 32 bytes for non-indexed params
|
||||
}
|
||||
|
||||
event, err := parser.ParseLog(log)
|
||||
if err == nil && event != nil {
|
||||
assert.Equal(t, ProtocolCamelotV3, event.Protocol)
|
||||
assert.NotNil(t, event.DecodedParams)
|
||||
}
|
||||
}
|
||||
|
||||
// Test TraderJoeV2Parser
|
||||
func TestTraderJoeV2Parser_New(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
|
||||
parser := NewTraderJoeV2Parser(client, logger)
|
||||
require.NotNil(t, parser)
|
||||
|
||||
tjParser, ok := parser.(*TraderJoeV2Parser)
|
||||
require.True(t, ok, "Parser should be TraderJoeV2Parser type")
|
||||
assert.Equal(t, ProtocolTraderJoeV2, tjParser.protocol)
|
||||
}
|
||||
|
||||
func TestTraderJoeV2Parser_GetSupportedContractTypes(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser)
|
||||
|
||||
types := parser.GetSupportedContractTypes()
|
||||
assert.Contains(t, types, ContractTypeFactory)
|
||||
assert.Contains(t, types, ContractTypeRouter)
|
||||
assert.Contains(t, types, ContractTypePool)
|
||||
}
|
||||
|
||||
func TestTraderJoeV2Parser_GetSupportedEventTypes(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser)
|
||||
|
||||
events := parser.GetSupportedEventTypes()
|
||||
assert.Contains(t, events, EventTypeSwap)
|
||||
assert.Contains(t, events, EventTypeLiquidityAdd)
|
||||
assert.Contains(t, events, EventTypeLiquidityRemove)
|
||||
assert.Contains(t, events, EventTypePoolCreated)
|
||||
}
|
||||
|
||||
func TestTraderJoeV2Parser_IsKnownContract(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser)
|
||||
|
||||
// Test known contract (factory)
|
||||
factoryAddr := common.HexToAddress("0x8e42f2F4101563bF679975178e880FD87d3eFd4e")
|
||||
assert.True(t, parser.IsKnownContract(factoryAddr))
|
||||
|
||||
// Test unknown contract
|
||||
unknownAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
assert.False(t, parser.IsKnownContract(unknownAddr))
|
||||
}
|
||||
|
||||
func TestTraderJoeV2Parser_ParseTransactionData(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser)
|
||||
|
||||
// Create mock transaction data for swapExactTokensForTokens
|
||||
data := make([]byte, 324)
|
||||
copy(data[0:4], []byte{0x38, 0xed, 0x17, 0x39}) // Function selector
|
||||
|
||||
// Add mock token addresses and amounts
|
||||
tokenX := common.HexToAddress("0xA0b86a33E6441f43E2e4A96439abFA2A69067ACD")
|
||||
tokenY := common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831")
|
||||
copy(data[16:32], tokenX.Bytes())
|
||||
copy(data[48:64], tokenY.Bytes())
|
||||
|
||||
tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), data)
|
||||
|
||||
event, err := parser.ParseTransactionData(tx)
|
||||
if err == nil && event != nil {
|
||||
assert.Equal(t, ProtocolTraderJoeV2, event.Protocol)
|
||||
assert.Equal(t, EventTypeSwap, event.EventType)
|
||||
assert.NotNil(t, event.DecodedParams)
|
||||
}
|
||||
}
|
||||
|
||||
// Test KyberElasticParser
|
||||
func TestKyberElasticParser_New(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
|
||||
parser := NewKyberElasticParser(client, logger)
|
||||
require.NotNil(t, parser)
|
||||
|
||||
kyberParser, ok := parser.(*KyberElasticParser)
|
||||
require.True(t, ok, "Parser should be KyberElasticParser type")
|
||||
assert.Equal(t, ProtocolKyberElastic, kyberParser.protocol)
|
||||
}
|
||||
|
||||
func TestKyberElasticParser_GetSupportedContractTypes(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewKyberElasticParser(client, logger).(*KyberElasticParser)
|
||||
|
||||
types := parser.GetSupportedContractTypes()
|
||||
assert.Contains(t, types, ContractTypeFactory)
|
||||
assert.Contains(t, types, ContractTypeRouter)
|
||||
assert.Contains(t, types, ContractTypePool)
|
||||
}
|
||||
|
||||
func TestKyberElasticParser_GetSupportedEventTypes(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewKyberElasticParser(client, logger).(*KyberElasticParser)
|
||||
|
||||
events := parser.GetSupportedEventTypes()
|
||||
assert.Contains(t, events, EventTypeSwap)
|
||||
assert.Contains(t, events, EventTypeLiquidityAdd)
|
||||
assert.Contains(t, events, EventTypeLiquidityRemove)
|
||||
assert.Contains(t, events, EventTypePoolCreated)
|
||||
assert.Contains(t, events, EventTypePositionUpdate)
|
||||
}
|
||||
|
||||
func TestKyberElasticParser_IsKnownContract(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewKyberElasticParser(client, logger).(*KyberElasticParser)
|
||||
|
||||
// Test known contract (factory)
|
||||
factoryAddr := common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a")
|
||||
assert.True(t, parser.IsKnownContract(factoryAddr))
|
||||
|
||||
// Test unknown contract
|
||||
unknownAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
assert.False(t, parser.IsKnownContract(unknownAddr))
|
||||
}
|
||||
|
||||
func TestKyberElasticParser_DecodeFunctionCall(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewKyberElasticParser(client, logger).(*KyberElasticParser)
|
||||
|
||||
// Create mock function call data for exactInputSingle
|
||||
data := make([]byte, 228)
|
||||
copy(data[0:4], []byte{0x04, 0xe4, 0x5a, 0xaf}) // Function selector
|
||||
|
||||
// Add mock token addresses
|
||||
tokenA := common.HexToAddress("0xA0b86a33E6441f43E2e4A96439abFA2A69067ACD")
|
||||
tokenB := common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831")
|
||||
copy(data[16:32], tokenA.Bytes())
|
||||
copy(data[48:64], tokenB.Bytes())
|
||||
|
||||
event, err := parser.DecodeFunctionCall(data)
|
||||
if err == nil && event != nil {
|
||||
assert.Equal(t, ProtocolKyberElastic, event.Protocol)
|
||||
assert.Equal(t, EventTypeSwap, event.EventType)
|
||||
assert.NotNil(t, event.DecodedParams)
|
||||
}
|
||||
}
|
||||
|
||||
// Test GetPoolInfo with mock RPC responses
|
||||
func TestCamelotV3Parser_GetPoolInfo_WithMockRPC(t *testing.T) {
|
||||
// Create a more sophisticated mock
|
||||
_ = &mockRPCClient{
|
||||
responses: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
logger := createMockLogger()
|
||||
parser := NewCamelotV3Parser(nil, logger).(*CamelotV3Parser)
|
||||
|
||||
poolAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
|
||||
// This would normally call the RPC, but we'll test the structure
|
||||
// In a real implementation, we'd use dependency injection or interfaces
|
||||
// for proper mocking of the RPC client
|
||||
|
||||
// Test that the method exists and has correct signature
|
||||
assert.NotNil(t, parser.GetPoolInfo)
|
||||
|
||||
// Test with nil client should return error
|
||||
_, err := parser.GetPoolInfo(poolAddr)
|
||||
assert.Error(t, err) // Should fail due to nil client
|
||||
}
|
||||
|
||||
// Integration test for DiscoverPools
|
||||
func TestTraderJoeV2Parser_DiscoverPools(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser)
|
||||
|
||||
// Test pool discovery
|
||||
pools, err := parser.DiscoverPools(1000000, 1000010)
|
||||
|
||||
// Should return empty pools due to mock, but no error
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pools)
|
||||
assert.Equal(t, 0, len(pools)) // Mock returns empty
|
||||
}
|
||||
|
||||
// Test ParseTransactionLogs
|
||||
func TestKyberElasticParser_ParseTransactionLogs(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewKyberElasticParser(client, logger).(*KyberElasticParser)
|
||||
|
||||
// Create mock transaction and receipt
|
||||
tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{})
|
||||
|
||||
receipt := &types.Receipt{
|
||||
BlockNumber: big.NewInt(1000000),
|
||||
Logs: []*types.Log{
|
||||
{
|
||||
Address: common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a"),
|
||||
Topics: []common.Hash{
|
||||
common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234"),
|
||||
},
|
||||
Data: make([]byte, 32),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
events, err := parser.ParseTransactionLogs(tx, receipt)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, events)
|
||||
// Events might be empty due to unknown topic, but should not error
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkCamelotV3Parser_ParseLog(b *testing.B) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser)
|
||||
|
||||
log := &types.Log{
|
||||
Address: common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"),
|
||||
Topics: []common.Hash{
|
||||
common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234"),
|
||||
},
|
||||
Data: make([]byte, 160),
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parser.ParseLog(log)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTraderJoeV2Parser_DecodeFunctionCall(b *testing.B) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser)
|
||||
|
||||
data := make([]byte, 324)
|
||||
copy(data[0:4], []byte{0x38, 0xed, 0x17, 0x39}) // Function selector
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parser.DecodeFunctionCall(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
func TestParsers_ErrorHandling(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
|
||||
parsers := []DEXParserInterface{
|
||||
NewCamelotV3Parser(client, logger),
|
||||
NewTraderJoeV2Parser(client, logger),
|
||||
NewKyberElasticParser(client, logger),
|
||||
}
|
||||
|
||||
for _, parser := range parsers {
|
||||
// Test with invalid data
|
||||
_, err := parser.DecodeFunctionCall([]byte{0x01, 0x02}) // Too short
|
||||
assert.Error(t, err, "Should error on too short data")
|
||||
|
||||
// Test with unknown function selector
|
||||
_, err = parser.DecodeFunctionCall([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00})
|
||||
assert.Error(t, err, "Should error on unknown selector")
|
||||
|
||||
// Test empty transaction data
|
||||
tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{})
|
||||
_, err = parser.ParseTransactionData(tx)
|
||||
assert.Error(t, err, "Should error on empty transaction data")
|
||||
}
|
||||
}
|
||||
|
||||
// Test protocol-specific features
|
||||
func TestTraderJoeV2Parser_LiquidityBookFeatures(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser)
|
||||
|
||||
// Test that liquidity book specific events are supported
|
||||
events := parser.GetSupportedEventTypes()
|
||||
assert.Contains(t, events, EventTypeSwap)
|
||||
assert.Contains(t, events, EventTypeLiquidityAdd)
|
||||
assert.Contains(t, events, EventTypeLiquidityRemove)
|
||||
|
||||
// Test that event signatures are properly initialized
|
||||
assert.NotEmpty(t, parser.eventSigs)
|
||||
|
||||
// Verify specific LB events exist
|
||||
hasSwapEvent := false
|
||||
hasDepositEvent := false
|
||||
hasWithdrawEvent := false
|
||||
|
||||
for _, sig := range parser.eventSigs {
|
||||
switch sig.Name {
|
||||
case "Swap":
|
||||
hasSwapEvent = true
|
||||
case "DepositedToBins":
|
||||
hasDepositEvent = true
|
||||
case "WithdrawnFromBins":
|
||||
hasWithdrawEvent = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, hasSwapEvent, "Should have Swap event")
|
||||
assert.True(t, hasDepositEvent, "Should have DepositedToBins event")
|
||||
assert.True(t, hasWithdrawEvent, "Should have WithdrawnFromBins event")
|
||||
}
|
||||
|
||||
func TestKyberElasticParser_ReinvestmentFeatures(t *testing.T) {
|
||||
client := createMockRPCClient()
|
||||
logger := createMockLogger()
|
||||
parser := NewKyberElasticParser(client, logger).(*KyberElasticParser)
|
||||
|
||||
// Test that Kyber-specific events are supported
|
||||
events := parser.GetSupportedEventTypes()
|
||||
assert.Contains(t, events, EventTypeSwap)
|
||||
assert.Contains(t, events, EventTypePositionUpdate)
|
||||
|
||||
// Test multiple router addresses (including meta router)
|
||||
routers := parser.contracts[ContractTypeRouter]
|
||||
assert.True(t, len(routers) >= 2, "Should have multiple router addresses")
|
||||
|
||||
// Test that factory address is set correctly
|
||||
factories := parser.contracts[ContractTypeFactory]
|
||||
assert.Equal(t, 1, len(factories), "Should have one factory address")
|
||||
expectedFactory := common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a")
|
||||
assert.Equal(t, expectedFactory, factories[0])
|
||||
}
|
||||
Reference in New Issue
Block a user