From 1696dc145e62a31f8fa062e06201c4c6e4596e05 Mon Sep 17 00:00:00 2001 From: CrossAgent Date: Mon, 25 May 2026 14:23:46 +0800 Subject: [PATCH 1/2] Filter contracts from EVM account list --- plugins/evm/dao/api.go | 7 ++++- plugins/evm/dao/api_cursor_test.go | 45 +++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/plugins/evm/dao/api.go b/plugins/evm/dao/api.go index 659cae0..703f03f 100644 --- a/plugins/evm/dao/api.go +++ b/plugins/evm/dao/api.go @@ -491,7 +491,12 @@ func (a AccountsJson) Cursor() string { func (a *ApiSrv) AccountsCursor(ctx context.Context, address string, limit int, before, after *string) ([]AccountsJson, map[string]interface{}) { var list []AccountsJson fetch := limit + 1 - q := sg.db.WithContext(ctx).Select("evm_account,balance").Model(&Account{}).Joins("join balance_accounts on evm_accounts.address=balance_accounts.address") + q := sg.db.WithContext(ctx). + Select("evm_accounts.evm_account,balance"). + Model(&Account{}). + Joins("join balance_accounts on evm_accounts.address=balance_accounts.address"). + Joins("left join evm_contracts on evm_contracts.address=evm_accounts.evm_account"). + Where("evm_contracts.address IS NULL") if address != "" { q = q.Where("evm_account = ?", address) } diff --git a/plugins/evm/dao/api_cursor_test.go b/plugins/evm/dao/api_cursor_test.go index 6236dbd..2cf6869 100644 --- a/plugins/evm/dao/api_cursor_test.go +++ b/plugins/evm/dao/api_cursor_test.go @@ -12,12 +12,21 @@ import ( "gorm.io/gorm" ) -func TestAccountsCursorFiltersByEvmAccount(t *testing.T) { +func setupAccountsCursorTest(t *testing.T) *gorm.DB { + t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) - require.NoError(t, db.AutoMigrate(&Account{}, &balanceModel.Account{})) + require.NoError(t, db.AutoMigrate(&Account{}, &Contract{}, &balanceModel.Account{})) + originalSg := sg sg = &Storage{db: db} + t.Cleanup(func() { sg = originalSg }) + + return db +} + +func TestAccountsCursorFiltersByEvmAccount(t *testing.T) { + db := setupAccountsCursorTest(t) ctx := context.Background() target := "0x63c4545ac01c77cc74044f25b8edea3880224577" @@ -37,11 +46,7 @@ func TestAccountsCursorFiltersByEvmAccount(t *testing.T) { } func TestAccountsCursorBeforeUsesBeforeCursor(t *testing.T) { - db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) - require.NoError(t, err) - require.NoError(t, db.AutoMigrate(&Account{}, &balanceModel.Account{})) - - sg = &Storage{db: db} + db := setupAccountsCursorTest(t) ctx := context.Background() accounts := []struct { @@ -69,3 +74,29 @@ func TestAccountsCursorBeforeUsesBeforeCursor(t *testing.T) { assert.Equal(t, false, page["has_previous_page"]) assert.Equal(t, true, page["has_next_page"]) } + +func TestAccountsCursorExcludesSmartContracts(t *testing.T) { + db := setupAccountsCursorTest(t) + + ctx := context.Background() + eoa := "0x0000000000000000000000000000000000000001" + contract := "0x0000000000000000000000000000000000000002" + + require.NoError(t, db.Create(&Account{Address: "substrate-eoa", EvmAccount: eoa}).Error) + require.NoError(t, db.Create(&Account{Address: "substrate-contract", EvmAccount: contract}).Error) + require.NoError(t, db.Create(&balanceModel.Account{Address: "substrate-eoa", Balance: decimal.NewFromInt(10)}).Error) + require.NoError(t, db.Create(&balanceModel.Account{Address: "substrate-contract", Balance: decimal.NewFromInt(20)}).Error) + require.NoError(t, db.Session(&gorm.Session{SkipHooks: true}).Create(&Contract{Address: contract}).Error) + + list, page := (&ApiSrv{}).AccountsCursor(ctx, "", 10, nil, nil) + + require.Len(t, list, 1) + assert.Equal(t, eoa, list[0].EvmAccount) + assert.Equal(t, decimal.NewFromInt(10), list[0].Balance) + assert.Equal(t, false, page["has_next_page"]) + + list, page = (&ApiSrv{}).AccountsCursor(ctx, contract, 10, nil, nil) + assert.Empty(t, list) + assert.Nil(t, page["start_cursor"]) + assert.Nil(t, page["end_cursor"]) +} From 590f6197f80ae194cb5c869116a5a3295d58d7b0 Mon Sep 17 00:00:00 2001 From: CrossAgent Date: Mon, 25 May 2026 17:52:52 +0800 Subject: [PATCH 2/2] Add EVM accounts route regression test --- plugins/evm/http/accounts_e2e_test.go | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 plugins/evm/http/accounts_e2e_test.go diff --git a/plugins/evm/http/accounts_e2e_test.go b/plugins/evm/http/accounts_e2e_test.go new file mode 100644 index 0000000..3663a94 --- /dev/null +++ b/plugins/evm/http/accounts_e2e_test.go @@ -0,0 +1,67 @@ +package http + +import ( + "bytes" + "encoding/json" + nethttp "net/http" + "net/http/httptest" + "strings" + "testing" + + balanceModel "github.com/itering/subscan/plugins/balance/model" + "github.com/itering/subscan/plugins/evm/dao" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestAccountsRouteExcludesSmartContracts(t *testing.T) { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + require.NoError(t, db.AutoMigrate(&dao.Account{}, &dao.Contract{}, &balanceModel.Account{})) + + dao.Init(db, nil) + originalSrv := srv + srv = &dao.ApiSrv{} + t.Cleanup(func() { srv = originalSrv }) + + eoa := "0x0000000000000000000000000000000000000001" + contract := "0x0000000000000000000000000000000000000002" + + require.NoError(t, db.Create(&dao.Account{Address: "substrate-eoa", EvmAccount: eoa}).Error) + require.NoError(t, db.Create(&dao.Account{Address: "substrate-contract", EvmAccount: contract}).Error) + require.NoError(t, db.Create(&balanceModel.Account{Address: "substrate-eoa", Balance: decimal.NewFromInt(10)}).Error) + require.NoError(t, db.Create(&balanceModel.Account{Address: "substrate-contract", Balance: decimal.NewFromInt(20)}).Error) + require.NoError(t, db.Session(&gorm.Session{SkipHooks: true}).Create(&dao.Contract{Address: contract}).Error) + + request := httptest.NewRequest( + nethttp.MethodPost, + "/api/plugin/evm/accounts", + strings.NewReader(`{"row":10}`), + ) + recorder := httptest.NewRecorder() + handler := nethttp.HandlerFunc(func(w nethttp.ResponseWriter, r *nethttp.Request) { + _ = accountsHandle(w, r) + }) + + handler.ServeHTTP(recorder, request) + require.Equal(t, nethttp.StatusOK, recorder.Code) + + var response struct { + Code int `json:"code"` + Data struct { + List []dao.AccountsJson `json:"list"` + } `json:"data"` + } + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response)) + require.Zero(t, response.Code) + require.Len(t, response.Data.List, 1) + assert.Equal(t, eoa, response.Data.List[0].EvmAccount) + assert.NotEqual(t, contract, response.Data.List[0].EvmAccount) + + var pretty bytes.Buffer + require.NoError(t, json.Indent(&pretty, recorder.Body.Bytes(), "", " ")) + t.Logf("POST /api/plugin/evm/accounts response with seeded evm_accounts and evm_contracts:\n%s", pretty.String()) +}