Files
indexer/evm/erc4337_test.go
zeekayandHanzo Dev 64519916bf the corporate name is Lux Industries Inc
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.

lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.

Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 13:01:04 -07:00

484 lines
13 KiB
Go

// Copyright (c) 2025 Lux Industries Inc
// SPDX-License-Identifier: MIT
package evm
import (
"context"
"database/sql"
"encoding/json"
"testing"
"time"
)
// TestNewERC4337Indexer tests ERC4337Indexer creation
func TestNewERC4337Indexer(t *testing.T) {
adapter := &Adapter{}
indexer := NewERC4337Indexer(adapter, nil)
if indexer == nil {
t.Fatal("NewERC4337Indexer returned nil")
}
if indexer.adapter != adapter {
t.Error("adapter not set correctly")
}
if len(indexer.entryPoints) != 2 {
t.Errorf("entryPoints length = %d, want 2", len(indexer.entryPoints))
}
if indexer.entryPoints[0] != EntryPointV06 {
t.Errorf("entryPoints[0] = %s, want %s", indexer.entryPoints[0], EntryPointV06)
}
if indexer.entryPoints[1] != EntryPointV07 {
t.Errorf("entryPoints[1] = %s, want %s", indexer.entryPoints[1], EntryPointV07)
}
}
// TestDecodeCallData tests callData decoding
func TestDecodeCallData(t *testing.T) {
tests := []struct {
name string
callData string
wantSelector string
wantFunction string
}{
{
name: "execute function",
callData: "0xb61d27f6000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000044",
wantSelector: "0xb61d27f6",
wantFunction: "execute",
},
{
name: "executeBatch function",
callData: "0x47e1da2a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002",
wantSelector: "0x47e1da2a",
wantFunction: "executeBatch",
},
{
name: "short data",
callData: "0x1234",
wantSelector: "",
wantFunction: "",
},
{
name: "empty data",
callData: "",
wantSelector: "",
wantFunction: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
selector, decoded := DecodeCallData(tt.callData)
if selector != tt.wantSelector {
t.Errorf("selector = %q, want %q", selector, tt.wantSelector)
}
if decoded != nil {
if fn, ok := decoded["function"].(string); ok && fn != tt.wantFunction {
t.Errorf("function = %q, want %q", fn, tt.wantFunction)
}
}
})
}
}
// TestDecodeInitCode tests initCode decoding
func TestDecodeInitCode(t *testing.T) {
tests := []struct {
name string
initCode string
wantFactory string
wantData string
}{
{
name: "valid initCode",
initCode: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d27896af4bb9c82d4af9d82e4c9a0",
wantFactory: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
wantData: "0x6af4bb9c82d4af9d82e4c9a0",
},
{
name: "short initCode",
initCode: "0x1234",
wantFactory: "",
wantData: "",
},
{
name: "empty initCode",
initCode: "",
wantFactory: "",
wantData: "",
},
{
name: "only factory address",
initCode: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
wantFactory: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
wantData: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
factory, data := DecodeInitCode(tt.initCode)
if factory != tt.wantFactory {
t.Errorf("factory = %q, want %q", factory, tt.wantFactory)
}
if data != tt.wantData {
t.Errorf("data = %q, want %q", data, tt.wantData)
}
})
}
}
// TestDecodePaymasterAndData tests paymasterAndData decoding
func TestDecodePaymasterAndData(t *testing.T) {
tests := []struct {
name string
paymasterData string
wantPaymaster string
wantData string
}{
{
name: "valid paymasterAndData",
paymasterData: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d27896af4bb9c82d4af9d82e4c9a0",
wantPaymaster: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
wantData: "0x6af4bb9c82d4af9d82e4c9a0",
},
{
name: "paymaster only",
paymasterData: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
wantPaymaster: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
wantData: "",
},
{
name: "short data",
paymasterData: "0x1234",
wantPaymaster: "",
wantData: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
paymaster, data := DecodePaymasterAndData(tt.paymasterData)
if paymaster != tt.wantPaymaster {
t.Errorf("paymaster = %q, want %q", paymaster, tt.wantPaymaster)
}
if data != tt.wantData {
t.Errorf("data = %q, want %q", data, tt.wantData)
}
})
}
}
// TestUserOperationValidation tests UserOperation field validation
func TestUserOperationValidation(t *testing.T) {
op := UserOperation{
Hash: "0x" + string(make([]byte, 64)),
Sender: "0x1234567890123456789012345678901234567890",
Nonce: "0x1",
InitCode: "0x",
CallData: "0xb61d27f6",
CallGasLimit: 100000,
VerificationGasLimit: 50000,
PreVerificationGas: 21000,
MaxFeePerGas: "1000000000",
MaxPriorityFeePerGas: "1000000",
PaymasterAndData: "0x",
Signature: "0x",
EntryPoint: EntryPointV06,
BlockNumber: 1000,
BlockHash: "0xblock",
TransactionHash: "0xtx",
BundlerAddress: "0xbundler",
Status: 1,
Timestamp: time.Now(),
EntryPointVersion: "v0.6",
}
// Test serialization
data, err := json.Marshal(op)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded UserOperation
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
// Verify fields
if decoded.Sender != op.Sender {
t.Errorf("Sender mismatch")
}
if decoded.CallGasLimit != op.CallGasLimit {
t.Errorf("CallGasLimit mismatch")
}
if decoded.EntryPointVersion != op.EntryPointVersion {
t.Errorf("EntryPointVersion mismatch")
}
}
// TestBundleSerialization tests Bundle JSON serialization
func TestBundleSerialization(t *testing.T) {
bundle := Bundle{
TransactionHash: "0xtx123",
BlockNumber: 1000,
BlockHash: "0xblock",
BundlerAddress: "0xbundler",
OperationCount: 3,
OperationHashes: []string{"0xop1", "0xop2", "0xop3"},
TotalGasUsed: 500000,
GasPrice: "1000000000",
Timestamp: time.Now(),
}
data, err := json.Marshal(bundle)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded Bundle
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.OperationCount != 3 {
t.Errorf("OperationCount = %d, want 3", decoded.OperationCount)
}
if len(decoded.OperationHashes) != 3 {
t.Errorf("OperationHashes length = %d, want 3", len(decoded.OperationHashes))
}
}
// TestBundlerSerialization tests Bundler JSON serialization
func TestBundlerSerialization(t *testing.T) {
now := time.Now()
bundler := Bundler{
Address: "0xbundler123",
TotalOperations: 1000,
TotalBundles: 500,
TotalGasSponsored: "1000000000000000000",
SuccessRate: 0.95,
FirstSeenBlock: 100,
LastSeenBlock: 1000,
FirstSeenTimestamp: now.Add(-time.Hour * 24),
LastSeenTimestamp: now,
}
data, err := json.Marshal(bundler)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded Bundler
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.TotalOperations != 1000 {
t.Errorf("TotalOperations = %d, want 1000", decoded.TotalOperations)
}
if decoded.SuccessRate != 0.95 {
t.Errorf("SuccessRate = %f, want 0.95", decoded.SuccessRate)
}
}
// TestPaymasterSerialization tests Paymaster JSON serialization
func TestPaymasterSerialization(t *testing.T) {
now := time.Now()
paymaster := Paymaster{
Address: "0xpaymaster123",
TotalOperations: 500,
TotalGasSponsored: "500000000000000000",
TotalEthSponsored: "0.5",
UniqueAccounts: 100,
FirstSeenBlock: 100,
LastSeenBlock: 1000,
FirstSeenTimestamp: now.Add(-time.Hour * 24),
LastSeenTimestamp: now,
}
data, err := json.Marshal(paymaster)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded Paymaster
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.UniqueAccounts != 100 {
t.Errorf("UniqueAccounts = %d, want 100", decoded.UniqueAccounts)
}
}
// TestAccountFactorySerialization tests AccountFactory JSON serialization
func TestAccountFactorySerialization(t *testing.T) {
now := time.Now()
factory := AccountFactory{
Address: "0xfactory123",
TotalAccounts: 1000,
ImplementationType: "SimpleAccount",
FirstSeenBlock: 100,
LastSeenBlock: 1000,
FirstSeenTimestamp: now.Add(-time.Hour * 24),
LastSeenTimestamp: now,
}
data, err := json.Marshal(factory)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded AccountFactory
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.ImplementationType != "SimpleAccount" {
t.Errorf("ImplementationType = %q, want SimpleAccount", decoded.ImplementationType)
}
}
// TestSmartAccountSerialization tests SmartAccount JSON serialization
func TestSmartAccountSerialization(t *testing.T) {
now := time.Now()
account := SmartAccount{
Address: "0xaccount123",
FactoryAddress: "0xfactory123",
DeploymentTxHash: "0xtx123",
DeploymentBlock: 100,
TotalOperations: 50,
TotalGasUsed: "1000000000000000",
CreatedAt: now.Add(-time.Hour * 24),
UpdatedAt: now,
}
data, err := json.Marshal(account)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded SmartAccount
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.DeploymentBlock != 100 {
t.Errorf("DeploymentBlock = %d, want 100", decoded.DeploymentBlock)
}
}
// TestTopicConstants tests ERC-4337 topic constants
func TestTopicConstants(t *testing.T) {
// UserOperationEvent topic should be 66 chars (0x + 64 hex)
if len(TopicUserOperationEvent) != 66 {
t.Errorf("TopicUserOperationEvent length = %d, want 66", len(TopicUserOperationEvent))
}
if TopicUserOperationEvent[:2] != "0x" {
t.Error("TopicUserOperationEvent should start with 0x")
}
// AccountDeployed topic
if len(TopicAccountDeployed) != 66 {
t.Errorf("TopicAccountDeployed length = %d, want 66", len(TopicAccountDeployed))
}
// BeforeExecution topic
if len(TopicBeforeExecution) != 66 {
t.Errorf("TopicBeforeExecution length = %d, want 66", len(TopicBeforeExecution))
}
}
// MockDB implements minimal database interface for testing
type MockDB struct {
*sql.DB
}
// TestCalculateUserOpHash tests hash calculation placeholder
func TestCalculateUserOpHash(t *testing.T) {
op := &UserOperation{
Hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
Sender: "0x1234567890123456789012345678901234567890",
Nonce: "0x1",
EntryPoint: EntryPointV06,
}
hash := CalculateUserOpHash(op, 96369)
if hash != op.Hash {
t.Errorf("CalculateUserOpHash returned %s, want %s", hash, op.Hash)
}
}
// Benchmarks
func BenchmarkDecodeCallData(b *testing.B) {
callData := "0xb61d27f6000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000044"
b.ResetTimer()
for i := 0; i < b.N; i++ {
DecodeCallData(callData)
}
}
func BenchmarkDecodeInitCode(b *testing.B) {
initCode := "0x5ff137d4b0fdcd49dca30c7cf57e578a026d27896af4bb9c82d4af9d82e4c9a0"
b.ResetTimer()
for i := 0; i < b.N; i++ {
DecodeInitCode(initCode)
}
}
func BenchmarkUserOperationMarshalComplete(b *testing.B) {
op := UserOperation{
Hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
Sender: "0x1234567890123456789012345678901234567890",
Nonce: "0x1",
InitCode: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d27896af4bb9c82d4af9d82e4c9a0",
CallData: "0xb61d27f6000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
CallGasLimit: 100000,
VerificationGasLimit: 50000,
PreVerificationGas: 21000,
MaxFeePerGas: "1000000000",
MaxPriorityFeePerGas: "1000000",
PaymasterAndData: "0x",
Signature: "0xsig",
EntryPoint: EntryPointV06,
BlockNumber: 1000,
BlockHash: "0xblock",
TransactionHash: "0xtx",
BundlerAddress: "0xbundler",
PaymasterAddress: "0xpaymaster",
FactoryAddress: "0xfactory",
Status: 1,
ActualGasCost: "500000",
ActualGasUsed: 50000,
Timestamp: time.Now(),
EntryPointVersion: "v0.6",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
json.Marshal(op)
}
}
// TestProcessBlockContext tests context handling
func TestProcessBlockContext(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
defer cancel()
indexer := &ERC4337Indexer{
adapter: nil,
db: nil,
entryPoints: []string{EntryPointV06},
}
// This should return quickly due to nil adapter
// In real tests, we'd mock the adapter
_ = indexer
_ = ctx
}