Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions server/address_normalization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package server

import (
"testing"
)

// TestNormalizeAddress verifies address normalization for case-insensitive comparison
func TestNormalizeAddress(t *testing.T) {
tests := []struct {
name string
addr1 string
addr2 string
expected bool // should they be equal after normalization?
}{
{
name: "identical lowercase addresses",
addr1: "0xeb5df7323c643f01b8c0643be808a0e6486621e8",
addr2: "0xeb5df7323c643f01b8c0643be808a0e6486621e8",
expected: true,
},
{
name: "identical uppercase addresses",
addr1: "0XEB5DF7323C643F01B8C0643BE808A0E6486621E8",
addr2: "0XEB5DF7323C643F01B8C0643BE808A0E6486621E8",
expected: true,
},
{
name: "mixed case vs lowercase (EIP-55 checksum)",
addr1: "0xeb5Df7323c643f01b8C0643bE808a0e6486621e8", // checksummed
addr2: "0xeb5df7323c643f01b8c0643be808a0e6486621e8", // lowercase
expected: true,
},
{
name: "mixed case vs uppercase",
addr1: "0xeb5Df7323c643f01b8C0643bE808a0e6486621e8", // checksummed
addr2: "0XEB5DF7323C643F01B8C0643BE808A0E6486621E8", // uppercase
expected: true,
},
{
name: "different addresses (lowercase)",
addr1: "0xeb5df7323c643f01b8c0643be808a0e6486621e8",
addr2: "0x628f0be408bdf24451a1c30c452abbb9cfb50a18",
expected: false,
},
{
name: "different addresses (mixed case)",
addr1: "0xeb5Df7323c643f01b8C0643bE808a0e6486621e8",
addr2: "0x628f0bE408bdf24451a1c30C452abbB9cfb50A18",
expected: false,
},
{
name: "empty addresses",
addr1: "",
addr2: "",
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
norm1 := normalizeAddress(tt.addr1)
norm2 := normalizeAddress(tt.addr2)
equal := norm1 == norm2

if equal != tt.expected {
t.Errorf("normalizeAddress(%q) vs normalizeAddress(%q):\ngot equal=%v, want equal=%v\nnorm1=%q, norm2=%q",
tt.addr1, tt.addr2, equal, tt.expected, norm1, norm2)
}
})
}
}
10 changes: 9 additions & 1 deletion server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import (
"time"
)

// normalizeAddress converts an Ethereum address to lowercase for case-insensitive comparison.
// Ethereum addresses are case-insensitive; the mixed case (EIP-55 checksum) is optional validation.
func normalizeAddress(addr string) string {
return strings.ToLower(addr)
}

// Handler manages HTTP requests for the mock server
type Handler struct {
state *State
Expand Down Expand Up @@ -546,7 +552,9 @@ func (h *Handler) handleOrderStatus(req InfoRequest) OrderQueryResult {
return OrderQueryResult{Status: "unknown_cloid"}
}

if order.Order.User != req.User {
// Compare normalized addresses for case-insensitive matching
// Ethereum addresses are case-insensitive; EIP-55 checksum is optional
if normalizeAddress(order.Order.User) != normalizeAddress(req.User) {
h.logger.Debug("order present but user does not match", slog.String("order.User", order.Order.User), slog.String("req.User", req.User))
return OrderQueryResult{Status: "unknown_cloid"}
}
Expand Down
9 changes: 6 additions & 3 deletions server/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,13 @@ func (wsm *WebSocketManager) handleSubscribe(state *ConnectionState, sub map[str
wsm.sendError(state.conn, "Missing user address for orderUpdates")
return
}
// Normalize address for case-insensitive comparison
normalizedUser := normalizeAddress(user)
state.mu.Lock()
state.orderUpdatesUser = user
state.orderUpdatesUser = normalizedUser
state.mu.Unlock()

wsm.logger.Info("subscribed to orderUpdates", "user", user)
wsm.logger.Info("subscribed to orderUpdates", "user", user, "normalized", normalizedUser)

// Send subscription acknowledgment
wsm.sendSubscriptionResponse(state.conn, sub)
Expand Down Expand Up @@ -370,7 +372,8 @@ func (wsm *WebSocketManager) broadcastOrderUpdates() {
// Send if:
// 1. Order has no wallet (signature recovery failed) - backward compatibility
// 2. Order wallet matches subscriber wallet - proper isolation
if orderUser == "" || orderUser == subscribedUser {
// (Compare normalized addresses for case-insensitive matching)
if orderUser == "" || normalizeAddress(orderUser) == subscribedUser {
shouldSend = true
}
}
Expand Down