From f8cf61edac810c77355daf8c58a9ecfb382abfba Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Mon, 9 Sep 2024 16:33:08 -0500 Subject: [PATCH 1/6] Use big.New(257) for big number math --- .../operatorShares/operatorShares.go | 33 ++++++++----- .../operatorShares/operatorShares_test.go | 40 +++++++++++++++- .../eigenState/stakerShares/stakerShares.go | 31 ++++++------ .../stakerShares/stakerShares_test.go | 8 ++-- internal/types/numbers/numbers.go | 10 ++++ internal/types/numbers/numbers_test.go | 48 +++++++++++++++++++ 6 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 internal/types/numbers/numbers.go create mode 100644 internal/types/numbers/numbers_test.go diff --git a/internal/eigenState/operatorShares/operatorShares.go b/internal/eigenState/operatorShares/operatorShares.go index 79ec0b60..6c10117d 100644 --- a/internal/eigenState/operatorShares/operatorShares.go +++ b/internal/eigenState/operatorShares/operatorShares.go @@ -9,8 +9,8 @@ import ( "github.com/Layr-Labs/go-sidecar/internal/eigenState/stateManager" "github.com/Layr-Labs/go-sidecar/internal/eigenState/types" "github.com/Layr-Labs/go-sidecar/internal/storage" + "github.com/Layr-Labs/go-sidecar/internal/types/numbers" "github.com/Layr-Labs/go-sidecar/internal/utils" - "github.com/holiman/uint256" "github.com/wealdtech/go-merkletree/v2" "github.com/wealdtech/go-merkletree/v2/keccak256" orderedmap "github.com/wk8/go-ordered-map/v2" @@ -18,6 +18,7 @@ import ( "golang.org/x/xerrors" "gorm.io/gorm" "gorm.io/gorm/clause" + "math/big" "slices" "sort" "strings" @@ -37,14 +38,15 @@ type OperatorShares struct { type AccumulatedStateChange struct { Operator string Strategy string - Shares *uint256.Int + Shares *big.Int BlockNumber uint64 + IsNegative bool } type OperatorSharesDiff struct { Operator string Strategy string - Shares *uint256.Int + Shares *big.Int BlockNumber uint64 IsNew bool } @@ -136,21 +138,21 @@ func (osm *OperatorSharesModel) GetStateTransitions() (types.StateTransitions[Ac operator := strings.ToLower(arguments[0].Value.(string)) sharesStr := outputData.Shares.String() - shares, err := uint256.FromDecimal(sharesStr) - if err != nil { - osm.logger.Sugar().Errorw("Failed to convert shares to uint256", - zap.Error(err), + shares, success := numbers.NewBig257().SetString(sharesStr, 10) + if !success { + osm.logger.Sugar().Errorw("Failed to convert shares to big.Int", zap.String("shares", sharesStr), zap.String("transactionHash", log.TransactionHash), zap.Uint64("transactionIndex", log.TransactionIndex), zap.Uint64("blockNumber", log.BlockNumber), ) - return nil, xerrors.Errorf("Failed to convert shares to uint256: %s", sharesStr) + return nil, xerrors.Errorf("Failed to convert shares to big.Int: %s", sharesStr) } + isNegative := false // All shares are emitted as ABS(shares), so we need to negate the shares if the event is a decrease if log.EventName == "OperatorSharesDecreased" { - shares = shares.Neg(shares) + isNegative = true } slotId := NewSlotId(operator, outputData.Strategy) @@ -161,10 +163,15 @@ func (osm *OperatorSharesModel) GetStateTransitions() (types.StateTransitions[Ac Strategy: outputData.Strategy, Shares: shares, BlockNumber: log.BlockNumber, + IsNegative: isNegative, } osm.stateAccumulator[log.BlockNumber][slotId] = record } else { - record.Shares = record.Shares.Add(record.Shares, shares) + if isNegative { + record.Shares = record.Shares.Sub(record.Shares, shares) + } else { + record.Shares = record.Shares.Add(record.Shares, shares) + } } return record, nil @@ -305,9 +312,9 @@ func (osm *OperatorSharesModel) prepareState(blockNumber uint64) ([]OperatorShar } if existingRecord, ok := mappedRecords[slotId]; ok { - existingShares, err := uint256.FromDecimal(existingRecord.Shares) - if err != nil { - osm.logger.Sugar().Errorw("Failed to convert existing shares to uint256", zap.Error(err)) + existingShares, success := numbers.NewBig257().SetString(existingRecord.Shares, 10) + if !success { + osm.logger.Sugar().Errorw("Failed to convert existing shares to big.Int") continue } prepared.Shares = existingShares.Add(existingShares, newState.Shares) diff --git a/internal/eigenState/operatorShares/operatorShares_test.go b/internal/eigenState/operatorShares/operatorShares_test.go index 8e18a4db..36444eff 100644 --- a/internal/eigenState/operatorShares/operatorShares_test.go +++ b/internal/eigenState/operatorShares/operatorShares_test.go @@ -2,7 +2,6 @@ package operatorShares import ( "database/sql" - "fmt" "github.com/Layr-Labs/go-sidecar/internal/config" "github.com/Layr-Labs/go-sidecar/internal/eigenState/stateManager" "github.com/Layr-Labs/go-sidecar/internal/logger" @@ -13,6 +12,7 @@ import ( "go.uber.org/zap" "gorm.io/gorm" "math/big" + "strings" "testing" "time" ) @@ -129,7 +129,43 @@ func Test_OperatorSharesState(t *testing.T) { stateRoot, err := model.GenerateStateRoot(blockNumber) assert.Nil(t, err) - fmt.Printf("StateRoot: %s\n", stateRoot) + assert.True(t, len(stateRoot) > 0) + + teardown(model) + }) + t.Run("Should handle state transition for operator shares decreased", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + blockNumber := uint64(200) + log := storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().DelegationManager, + Arguments: `[{"Name": "operator", "Type": "address", "Value": "0x32f766cf7BC7dEE7F65573587BECd7AdB2a5CC7f"}, {"Name": "staker", "Type": "address", "Value": ""}, {"Name": "strategy", "Type": "address", "Value": ""}, {"Name": "shares", "Type": "uint256", "Value": ""}]`, + EventName: "OperatorSharesDecreased", + LogIndex: big.NewInt(400).Uint64(), + OutputData: `{"shares": 1670000000000000000000, "staker": "0x32f766cf7bc7dee7f65573587becd7adb2a5cc7f", "strategy": "0x80528d6e9a2babfc766965e0e26d5ab08d9cfaf9"}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + + model, err := NewOperatorSharesModel(esm, grm, cfg.Network, cfg.Environment, l, cfg) + assert.Nil(t, err) + + err = model.InitBlockProcessing(blockNumber) + assert.Nil(t, err) + + stateChange, err := model.HandleStateChange(&log) + assert.Nil(t, err) + assert.NotNil(t, stateChange) + + stateChangeTyped := stateChange.(*AccumulatedStateChange) + + assert.Equal(t, "1670000000000000000000", stateChangeTyped.Shares.String()) + assert.Equal(t, true, stateChangeTyped.IsNegative) + assert.Equal(t, strings.ToLower("0x32f766cf7BC7dEE7F65573587BECd7AdB2a5CC7f"), stateChangeTyped.Operator) + assert.Equal(t, "0x80528d6e9a2babfc766965e0e26d5ab08d9cfaf9", stateChangeTyped.Strategy) teardown(model) }) diff --git a/internal/eigenState/stakerShares/stakerShares.go b/internal/eigenState/stakerShares/stakerShares.go index 2bdbec3c..d2e9fa8c 100644 --- a/internal/eigenState/stakerShares/stakerShares.go +++ b/internal/eigenState/stakerShares/stakerShares.go @@ -9,8 +9,8 @@ import ( "github.com/Layr-Labs/go-sidecar/internal/eigenState/stateManager" "github.com/Layr-Labs/go-sidecar/internal/eigenState/types" "github.com/Layr-Labs/go-sidecar/internal/storage" + "github.com/Layr-Labs/go-sidecar/internal/types/numbers" "github.com/Layr-Labs/go-sidecar/internal/utils" - "github.com/holiman/uint256" "github.com/wealdtech/go-merkletree/v2" "github.com/wealdtech/go-merkletree/v2/keccak256" orderedmap "github.com/wk8/go-ordered-map/v2" @@ -18,6 +18,7 @@ import ( "golang.org/x/xerrors" "gorm.io/gorm" "gorm.io/gorm/clause" + "math/big" "slices" "sort" "strings" @@ -35,7 +36,7 @@ type StakerShares struct { type AccumulatedStateChange struct { Staker string Strategy string - Shares *uint256.Int + Shares *big.Int BlockNumber uint64 IsNegative bool } @@ -43,7 +44,7 @@ type AccumulatedStateChange struct { type StakerSharesDiff struct { Staker string Strategy string - Shares *uint256.Int + Shares *big.Int BlockNumber uint64 IsNew bool } @@ -137,9 +138,9 @@ func (ss *StakerSharesModel) handleStakerDepositEvent(log *storage.TransactionLo return nil, xerrors.Errorf("No staker address found in event") } - shares, err := uint256.FromDecimal(outputData.Shares.String()) - if err != nil { - return nil, xerrors.Errorf("Failed to convert shares to uint256: %s", outputData.Shares) + shares, success := numbers.NewBig257().SetString(outputData.Shares.String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to convert shares to big.Int: %s", outputData.Shares) } return &AccumulatedStateChange{ @@ -180,9 +181,9 @@ func (ss *StakerSharesModel) handlePodSharesUpdatedEvent(log *storage.Transactio sharesDeltaStr := outputData.SharesDelta.String() - sharesDelta, err := uint256.FromDecimal(sharesDeltaStr) - if err != nil { - return nil, xerrors.Errorf("Failed to convert shares to uint256: %s", sharesDelta) + sharesDelta, success := numbers.NewBig257().SetString(sharesDeltaStr, 10) + if !success { + return nil, xerrors.Errorf("Failed to convert shares to big.Int: %s", sharesDelta) } return &AccumulatedStateChange{ @@ -211,9 +212,9 @@ func (ss *StakerSharesModel) handleM1StakerWithdrawals(log *storage.TransactionL return nil, xerrors.Errorf("No staker address found in event") } - shares, err := uint256.FromDecimal(outputData.Shares.String()) - if err != nil { - return nil, xerrors.Errorf("Failed to convert shares to uint256: %s", outputData.Shares) + shares, success := numbers.NewBig257().SetString(outputData.Shares.String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to convert shares to big.Int: %s", outputData.Shares) } return &AccumulatedStateChange{ @@ -425,9 +426,9 @@ func (ss *StakerSharesModel) prepareState(blockNumber uint64) ([]StakerSharesDif } if existingRecord, ok := mappedRecords[slotId]; ok { - existingShares, err := uint256.FromDecimal(existingRecord.Shares) - if err != nil { - ss.logger.Sugar().Errorw("Failed to convert existing shares to uint256", zap.Error(err)) + existingShares, success := numbers.NewBig257().SetString(existingRecord.Shares, 10) + if !success { + ss.logger.Sugar().Errorw("Failed to convert existing shares to big.Int") continue } prepared.Shares = existingShares.Add(existingShares, newState.Shares) diff --git a/internal/eigenState/stakerShares/stakerShares_test.go b/internal/eigenState/stakerShares/stakerShares_test.go index ec2487fb..064047af 100644 --- a/internal/eigenState/stakerShares/stakerShares_test.go +++ b/internal/eigenState/stakerShares/stakerShares_test.go @@ -7,7 +7,7 @@ import ( "github.com/Layr-Labs/go-sidecar/internal/sqlite/migrations" "github.com/Layr-Labs/go-sidecar/internal/storage" "github.com/Layr-Labs/go-sidecar/internal/tests" - "github.com/holiman/uint256" + "github.com/Layr-Labs/go-sidecar/internal/types/numbers" "github.com/stretchr/testify/assert" "go.uber.org/zap" "gorm.io/gorm" @@ -83,7 +83,7 @@ func Test_StakerSharesState(t *testing.T) { typedChange := change.(*AccumulatedStateChange) - expectedShares, _ := uint256.FromDecimal("159925690037480381") + expectedShares, _ := numbers.NewBig257().SetString("159925690037480381", 10) assert.Equal(t, expectedShares, typedChange.Shares) assert.Equal(t, "0xaf6fb48ac4a60c61a64124ce9dc28f508dc8de8d", typedChange.Staker) assert.Equal(t, "0x7d704507b76571a51d9cae8addabbfd0ba0e63d3", typedChange.Strategy) @@ -118,7 +118,7 @@ func Test_StakerSharesState(t *testing.T) { typedChange := change.(*AccumulatedStateChange) - expectedShares, _ := uint256.FromDecimal("246393621132195985") + expectedShares, _ := numbers.NewBig257().SetString("246393621132195985", 10) assert.Equal(t, expectedShares, typedChange.Shares) assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Staker) assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Strategy) @@ -153,7 +153,7 @@ func Test_StakerSharesState(t *testing.T) { typedChange := change.(*AccumulatedStateChange) - expectedShares, _ := uint256.FromDecimal("32000000000000000000") + expectedShares, _ := numbers.NewBig257().SetString("32000000000000000000", 10) assert.Equal(t, expectedShares, typedChange.Shares) assert.Equal(t, strings.ToLower("0x0808D4689B347D499a96f139A5fC5B5101258406"), typedChange.Staker) assert.Equal(t, "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", typedChange.Strategy) diff --git a/internal/types/numbers/numbers.go b/internal/types/numbers/numbers.go new file mode 100644 index 00000000..3e9d62c0 --- /dev/null +++ b/internal/types/numbers/numbers.go @@ -0,0 +1,10 @@ +package numbers + +import "math/big" + +// NewBig257 returns a new big.Int with a size of 257 bits +// This allows us to fully support math on uint256 numbers +// as well as int256 numbers used for EigenPods +func NewBig257() *big.Int { + return big.NewInt(257) +} diff --git a/internal/types/numbers/numbers_test.go b/internal/types/numbers/numbers_test.go new file mode 100644 index 00000000..681a0f88 --- /dev/null +++ b/internal/types/numbers/numbers_test.go @@ -0,0 +1,48 @@ +package numbers + +import ( + "github.com/stretchr/testify/assert" + "math/big" + "testing" +) + +func Test_numbers(t *testing.T) { + t.Run("Test that big.Int can produce negative numbers", func(t *testing.T) { + startingNum := big.Int{} + startingNum.SetString("10", 10) + + amountToSubtract := big.Int{} + amountToSubtract.SetString("20", 10) + + assert.Equal(t, "-10", amountToSubtract.Sub(&startingNum, &amountToSubtract).String()) + }) + t.Run("Test that big.NewInt(257) can produce negative numbers", func(t *testing.T) { + startingNum := big.NewInt(257) + startingNum.SetString("10", 10) + + amountToSubtract := big.NewInt(257) + amountToSubtract.SetString("20", 10) + + assert.Equal(t, "-10", amountToSubtract.Sub(startingNum, amountToSubtract).String()) + }) + t.Run("Test that Big257 can produce negative numbers", func(t *testing.T) { + startingNum, success := NewBig257().SetString("10", 10) + assert.True(t, success) + + amountToSubtract, success := NewBig257().SetString("20", 10) + assert.True(t, success) + + assert.Equal(t, "-10", amountToSubtract.Sub(startingNum, amountToSubtract).String()) + }) + t.Run("Test a really big number", func(t *testing.T) { + startingNum, success := NewBig257().SetString("13389173346000000000000000", 10) + assert.True(t, success) + + assert.Equal(t, "13389173346000000000000000", startingNum.String()) + + amountToSubtract, success := NewBig257().SetString("20", 10) + assert.True(t, success) + + assert.Equal(t, "13389173345999999999999980", amountToSubtract.Sub(startingNum, amountToSubtract).String()) + }) +} From f7bc0cb7789c761a4be26105129a6cd42c6fff73 Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Mon, 9 Sep 2024 16:58:31 -0500 Subject: [PATCH 2/6] Add support for M2 WithdrawalQueued --- .../eigenState/stakerShares/stakerShares.go | 123 +++++++++++++++--- .../stakerShares/stakerShares_test.go | 65 +++++++-- 2 files changed, 153 insertions(+), 35 deletions(-) diff --git a/internal/eigenState/stakerShares/stakerShares.go b/internal/eigenState/stakerShares/stakerShares.go index d2e9fa8c..231801db 100644 --- a/internal/eigenState/stakerShares/stakerShares.go +++ b/internal/eigenState/stakerShares/stakerShares.go @@ -2,6 +2,7 @@ package stakerShares import ( "database/sql" + "encoding/hex" "encoding/json" "fmt" "github.com/Layr-Labs/go-sidecar/internal/config" @@ -226,16 +227,77 @@ func (ss *StakerSharesModel) handleM1StakerWithdrawals(log *storage.TransactionL }, nil } -func (ss *StakerSharesModel) handleM2StakerWithdrawals(log *storage.TransactionLog) (*AccumulatedStateChange, error) { +// handleMigratedM2StakerWithdrawals handles the WithdrawalMigrated event from the DelegationManager contract +// +// Since we have already counted M1 withdrawals due to processing events block-by-block, we need to handle not double subtracting. +// Assuming that M2 WithdrawalQueued events always result in a subtraction, if we encounter a migration event, we need +// to add the amount back to the shares to get the correct final state. +func (ss *StakerSharesModel) handleMigratedM2StakerWithdrawals(log *storage.TransactionLog) (*AccumulatedStateChange, error) { // TODO(seanmcgary): come back to this... return nil, nil } -func (ss *StakerSharesModel) GetStateTransitions() (types.StateTransitions[AccumulatedStateChange], []uint64) { - stateChanges := make(types.StateTransitions[AccumulatedStateChange]) +type m2WithdrawalOutputData struct { + Withdrawal struct { + Nonce int `json:"nonce"` + Shares []json.Number `json:"shares"` + Staker string `json:"staker"` + StartBlock uint64 `json:"startBlock"` + Strategies []string `json:"strategies"` + } `json:"withdrawal"` + WithdrawalRoot []byte `json:"withdrawalRoot"` + WithdrawalRootString string +} + +func parseLogOutputForM2WithdrawalEvent(outputDataStr string) (*m2WithdrawalOutputData, error) { + outputData := &m2WithdrawalOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + outputData.Withdrawal.Staker = strings.ToLower(outputData.Withdrawal.Staker) + outputData.WithdrawalRootString = hex.EncodeToString(outputData.WithdrawalRoot) + return outputData, err +} + +// handleM2QueuedWithdrawal handles the WithdrawalQueued event from the DelegationManager contract for M2 +func (ss *StakerSharesModel) handleM2QueuedWithdrawal(log *storage.TransactionLog) ([]*AccumulatedStateChange, error) { + outputData, err := parseLogOutputForM2WithdrawalEvent(log.OutputData) + if err != nil { + return nil, err + } + + records := make([]*AccumulatedStateChange, 0) - stateChanges[0] = func(log *storage.TransactionLog) (*AccumulatedStateChange, error) { - var parsedRecord *AccumulatedStateChange + for i, strategy := range outputData.Withdrawal.Strategies { + shares, success := numbers.NewBig257().SetString(outputData.Withdrawal.Shares[i].String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to convert shares to big.Int: %s", outputData.Withdrawal.Shares[i]) + } + r := &AccumulatedStateChange{ + Staker: outputData.Withdrawal.Staker, + Strategy: strategy, + Shares: shares, + BlockNumber: log.BlockNumber, + IsNegative: true, + } + records = append(records, r) + } + return records, nil +} + +type AccumulatedStateChanges struct { + Changes []*AccumulatedStateChange +} + +func (ss *StakerSharesModel) GetStateTransitions() (types.StateTransitions[AccumulatedStateChanges], []uint64) { + stateChanges := make(types.StateTransitions[AccumulatedStateChanges]) + + stateChanges[0] = func(log *storage.TransactionLog) (*AccumulatedStateChanges, error) { + var parsedRecords []*AccumulatedStateChange var err error contractAddresses := ss.globalConfig.GetContractsMapForEnvAndNetwork() @@ -243,13 +305,30 @@ func (ss *StakerSharesModel) GetStateTransitions() (types.StateTransitions[Accum // Staker shares is a bit more complex and has 4 possible contract/event combinations // that we need to handle if log.Address == contractAddresses.StrategyManager && log.EventName == "Deposit" { - parsedRecord, err = ss.handleStakerDepositEvent(log) + record, err := ss.handleStakerDepositEvent(log) + if err == nil { + parsedRecords = append(parsedRecords, record) + } } else if log.Address == contractAddresses.EigenpodManager && log.EventName == "PodSharesUpdated" { - parsedRecord, err = ss.handlePodSharesUpdatedEvent(log) + record, err := ss.handlePodSharesUpdatedEvent(log) + if err == nil { + parsedRecords = append(parsedRecords, record) + } } else if log.Address == contractAddresses.StrategyManager && log.EventName == "ShareWithdrawalQueued" && log.TransactionHash != "0x62eb0d0865b2636c74ed146e2d161e39e42b09bac7f86b8905fc7a830935dc1e" { - parsedRecord, err = ss.handleM1StakerWithdrawals(log) + record, err := ss.handleM1StakerWithdrawals(log) + if err == nil { + parsedRecords = append(parsedRecords, record) + } } else if log.Address == contractAddresses.DelegationManager && log.EventName == "WithdrawalMigrated" { - parsedRecord, err = ss.handleM2StakerWithdrawals(log) + record, err := ss.handleMigratedM2StakerWithdrawals(log) + if err == nil { + parsedRecords = append(parsedRecords, record) + } + } else if log.Address == contractAddresses.DelegationManager && log.EventName == "WithdrawalQueued" { + records, err := ss.handleM2QueuedWithdrawal(log) + if err == nil { + parsedRecords = append(parsedRecords, records...) + } } else { ss.logger.Sugar().Debugw("Got stakerShares event that we don't handle", zap.String("eventName", log.EventName), @@ -259,7 +338,7 @@ func (ss *StakerSharesModel) GetStateTransitions() (types.StateTransitions[Accum if err != nil { return nil, err } - if parsedRecord == nil { + if parsedRecords == nil { return nil, nil } @@ -268,21 +347,22 @@ func (ss *StakerSharesModel) GetStateTransitions() (types.StateTransitions[Accum return nil, xerrors.Errorf("No state accumulator found for block %d", log.BlockNumber) } - slotId := NewSlotId(parsedRecord.Staker, parsedRecord.Strategy) - record, ok := ss.stateAccumulator[log.BlockNumber][slotId] - if !ok { - record = parsedRecord - ss.stateAccumulator[log.BlockNumber][slotId] = record - } else { - if record.IsNegative { - record.Shares = record.Shares.Sub(record.Shares, parsedRecord.Shares) + for _, parsedRecord := range parsedRecords { + slotId := NewSlotId(parsedRecord.Staker, parsedRecord.Strategy) + record, ok := ss.stateAccumulator[log.BlockNumber][slotId] + if !ok { + record = parsedRecord + ss.stateAccumulator[log.BlockNumber][slotId] = record } else { - record.Shares = record.Shares.Add(record.Shares, parsedRecord.Shares) + if record.IsNegative { + record.Shares = record.Shares.Sub(record.Shares, parsedRecord.Shares) + } else { + record.Shares = record.Shares.Add(record.Shares, parsedRecord.Shares) + } } - } - return record, nil + return &AccumulatedStateChanges{Changes: parsedRecords}, nil } // Create an ordered list of block numbers @@ -303,6 +383,7 @@ func (ss *StakerSharesModel) getContractAddressesForEnvironment() map[string][]s return map[string][]string{ contracts.DelegationManager: []string{ "WithdrawalMigrated", + "WithdrawalQueued", }, contracts.StrategyManager: []string{ "Deposit", diff --git a/internal/eigenState/stakerShares/stakerShares_test.go b/internal/eigenState/stakerShares/stakerShares_test.go index 064047af..83067739 100644 --- a/internal/eigenState/stakerShares/stakerShares_test.go +++ b/internal/eigenState/stakerShares/stakerShares_test.go @@ -81,12 +81,14 @@ func Test_StakerSharesState(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, change) - typedChange := change.(*AccumulatedStateChange) + typedChange := change.(*AccumulatedStateChanges) + + assert.Equal(t, 1, len(typedChange.Changes)) expectedShares, _ := numbers.NewBig257().SetString("159925690037480381", 10) - assert.Equal(t, expectedShares, typedChange.Shares) - assert.Equal(t, "0xaf6fb48ac4a60c61a64124ce9dc28f508dc8de8d", typedChange.Staker) - assert.Equal(t, "0x7d704507b76571a51d9cae8addabbfd0ba0e63d3", typedChange.Strategy) + assert.Equal(t, expectedShares, typedChange.Changes[0].Shares) + assert.Equal(t, "0xaf6fb48ac4a60c61a64124ce9dc28f508dc8de8d", typedChange.Changes[0].Staker) + assert.Equal(t, "0x7d704507b76571a51d9cae8addabbfd0ba0e63d3", typedChange.Changes[0].Strategy) teardown(model) }) @@ -116,12 +118,13 @@ func Test_StakerSharesState(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, change) - typedChange := change.(*AccumulatedStateChange) + typedChange := change.(*AccumulatedStateChanges) + assert.Equal(t, 1, len(typedChange.Changes)) expectedShares, _ := numbers.NewBig257().SetString("246393621132195985", 10) - assert.Equal(t, expectedShares, typedChange.Shares) - assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Staker) - assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Strategy) + assert.Equal(t, expectedShares, typedChange.Changes[0].Shares) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Changes[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Changes[0].Strategy) teardown(model) }) @@ -151,16 +154,50 @@ func Test_StakerSharesState(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, change) - typedChange := change.(*AccumulatedStateChange) + typedChange := change.(*AccumulatedStateChanges) + assert.Equal(t, 1, len(typedChange.Changes)) expectedShares, _ := numbers.NewBig257().SetString("32000000000000000000", 10) - assert.Equal(t, expectedShares, typedChange.Shares) - assert.Equal(t, strings.ToLower("0x0808D4689B347D499a96f139A5fC5B5101258406"), typedChange.Staker) - assert.Equal(t, "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", typedChange.Strategy) + assert.Equal(t, expectedShares, typedChange.Changes[0].Shares) + assert.Equal(t, strings.ToLower("0x0808D4689B347D499a96f139A5fC5B5101258406"), typedChange.Changes[0].Staker) + assert.Equal(t, "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", typedChange.Changes[0].Strategy) teardown(model) }) - t.Run("Should capture M2 migrated withdrawals", func(t *testing.T) { - t.Skip("M2 migration is not yet implemented") + t.Run("Should capture M2 withdrawals", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + blockNumber := uint64(200) + log := storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(300).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().DelegationManager, + Arguments: `[{"Name": "withdrawalRoot", "Type": "bytes32", "Value": ""}, {"Name": "withdrawal", "Type": "(address,address,address,uint256,uint32,address[],uint256[])", "Value": ""}]`, + EventName: "WithdrawalQueued", + LogIndex: big.NewInt(600).Uint64(), + OutputData: `{"withdrawal": {"nonce": 0, "shares": [1000000000000000000], "staker": "0x3c42cd72639e3e8d11ab8d0072cc13bd5d8aa83c", "startBlock": 1215690, "strategies": ["0xd523267698c81a372191136e477fdebfa33d9fb4"], "withdrawer": "0x3c42cd72639e3e8d11ab8d0072cc13bd5d8aa83c", "delegatedTo": "0x2177dee1f66d6dbfbf517d9c4f316024c6a21aeb"}, "withdrawalRoot": [24, 23, 49, 137, 14, 63, 119, 12, 234, 225, 63, 35, 109, 249, 112, 24, 241, 118, 212, 52, 22, 107, 202, 56, 105, 37, 68, 47, 169, 23, 142, 135]}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + + model, err := NewStakerSharesModel(esm, grm, cfg.Network, cfg.Environment, l, cfg) + + err = model.InitBlockProcessing(blockNumber) + assert.Nil(t, err) + + change, err := model.HandleStateChange(&log) + assert.Nil(t, err) + assert.NotNil(t, change) + + typedChange := change.(*AccumulatedStateChanges) + assert.Equal(t, 1, len(typedChange.Changes)) + + expectedShares, _ := numbers.NewBig257().SetString("1000000000000000000", 10) + assert.Equal(t, expectedShares, typedChange.Changes[0].Shares) + assert.Equal(t, strings.ToLower("0x3c42cd72639e3e8d11ab8d0072cc13bd5d8aa83c"), typedChange.Changes[0].Staker) + assert.Equal(t, "0xd523267698c81a372191136e477fdebfa33d9fb4", typedChange.Changes[0].Strategy) + + teardown(model) }) } From f85e5d808ad5af5bb92b0da9629805355ec6d0af Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Mon, 9 Sep 2024 22:21:29 -0500 Subject: [PATCH 3/6] Register custom bytes_to_hex function --- internal/sqlite/sqlite.go | 25 ++++++++++++++++++-- internal/sqlite/sqlite_test.go | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 internal/sqlite/sqlite_test.go diff --git a/internal/sqlite/sqlite.go b/internal/sqlite/sqlite.go index 562c7728..0cc1dcdb 100644 --- a/internal/sqlite/sqlite.go +++ b/internal/sqlite/sqlite.go @@ -1,15 +1,35 @@ package sqlite import ( + "database/sql" + "encoding/hex" + "encoding/json" "fmt" + goSqlite "github.com/mattn/go-sqlite3" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) +func bytesToHex(jsonByteArray string) (string, error) { + jsonBytes := make([]byte, 0) + err := json.Unmarshal([]byte(jsonByteArray), &jsonBytes) + if err != nil { + return "", err + } + return hex.EncodeToString(jsonBytes), nil +} + func NewSqlite(path string) gorm.Dialector { - db := sqlite.Open(path) - return db + sql.Register("sqlite3_with_extensions", &goSqlite.SQLiteDriver{ + ConnectHook: func(conn *goSqlite.SQLiteConn) error { + return conn.RegisterFunc("bytes_to_hex", bytesToHex, true) + }, + }) + return &sqlite.Dialector{ + DriverName: "sqlite3_with_extensions", + DSN: path, + } } func NewGormSqliteFromSqlite(sqlite gorm.Dialector) (*gorm.DB, error) { @@ -34,6 +54,7 @@ func NewGormSqliteFromSqlite(sqlite gorm.Dialector) (*gorm.DB, error) { return nil, res.Error } } + return db, nil } diff --git a/internal/sqlite/sqlite_test.go b/internal/sqlite/sqlite_test.go new file mode 100644 index 00000000..cd8d6588 --- /dev/null +++ b/internal/sqlite/sqlite_test.go @@ -0,0 +1,43 @@ +package sqlite + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_Sqlite(t *testing.T) { + // t.Run("Should create a new GormSqlite", func(t *testing.T) { + // s := NewSqlite("file::memory:?cache=shared") + // grm, err := NewGormSqliteFromSqlite(s) + // assert.Nil(t, err) + // assert.NotNil(t, grm) + // + // db, err := grm.DB() + // defer db.Close() + // }) + t.Run("Should use the bytesToHex function", func(t *testing.T) { + query := ` + with json_values as ( + select + cast('{"newWithdrawalRoot": [218, 200, 138, 86, 38, 9, 156, 119, 73, 13, 168, 40, 209, 43, 238, 83, 234, 177, 230, 73, 120, 205, 255, 143, 255, 216, 51, 209, 137, 100, 163, 233] }' as text) as json_col + from (select 1) + ) + select + bytes_to_hex(json_extract(json_col, '$.newWithdrawalRoot')) AS withdrawal_hex + from json_values + limit 1 + ` + s := NewSqlite("file::memory:?cache=shared") + grm, err := NewGormSqliteFromSqlite(s) + assert.Nil(t, err) + + type results struct { + WithdrawalHex string + } + + hexValue := &results{} + res := grm.Raw(query).Scan(&hexValue) + assert.Nil(t, res.Error) + assert.Equal(t, "dac88a5626099c77490da828d12be653eab1e64978cdff8fffD337d18964a3e9", hexValue.WithdrawalHex) + }) +} From 4790cd96ff812fbf16a291d2b9854c5d0736f427 Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Tue, 10 Sep 2024 10:27:51 -0500 Subject: [PATCH 4/6] Fully test M1 and M1 --> M2 migration --- cmd/debugger/main.go | 134 ++++++++ go.mod | 2 +- go.sum | 2 + .../eigenState/stakerShares/stakerShares.go | 107 ++++-- .../stakerShares/stakerShares_test.go | 321 +++++++++++++++++- internal/indexer/transactionLogs.go | 2 +- 6 files changed, 544 insertions(+), 24 deletions(-) create mode 100644 cmd/debugger/main.go diff --git a/cmd/debugger/main.go b/cmd/debugger/main.go new file mode 100644 index 00000000..73c80ce4 --- /dev/null +++ b/cmd/debugger/main.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "fmt" + "github.com/Layr-Labs/go-sidecar/internal/clients/ethereum" + "github.com/Layr-Labs/go-sidecar/internal/clients/etherscan" + "github.com/Layr-Labs/go-sidecar/internal/config" + "github.com/Layr-Labs/go-sidecar/internal/contractManager" + "github.com/Layr-Labs/go-sidecar/internal/contractStore/sqliteContractStore" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/avsOperators" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/operatorShares" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/stakerDelegations" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/stakerShares" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/stateManager" + "github.com/Layr-Labs/go-sidecar/internal/fetcher" + "github.com/Layr-Labs/go-sidecar/internal/indexer" + "github.com/Layr-Labs/go-sidecar/internal/logger" + "github.com/Layr-Labs/go-sidecar/internal/metrics" + "github.com/Layr-Labs/go-sidecar/internal/pipeline" + "github.com/Layr-Labs/go-sidecar/internal/sidecar" + "github.com/Layr-Labs/go-sidecar/internal/sqlite" + "github.com/Layr-Labs/go-sidecar/internal/sqlite/migrations" + sqliteBlockStore "github.com/Layr-Labs/go-sidecar/internal/storage/sqlite" + "go.uber.org/zap" + "log" +) + +func main() { + ctx := context.Background() + cfg := config.NewConfig() + + fmt.Printf("Config: %+v\n", cfg) + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug}) + + sdc, err := metrics.InitStatsdClient(cfg.StatsdUrl) + if err != nil { + l.Sugar().Fatal("Failed to setup statsd client", zap.Error(err)) + } + + etherscanClient := etherscan.NewEtherscanClient(cfg, l) + client := ethereum.NewClient(cfg.EthereumRpcConfig.BaseUrl, l) + + db := sqlite.NewSqlite(cfg.SqliteConfig.GetSqlitePath()) + + grm, err := sqlite.NewGormSqliteFromSqlite(db) + if err != nil { + l.Error("Failed to create gorm instance", zap.Error(err)) + panic(err) + } + + migrator := migrations.NewSqliteMigrator(grm, l) + if err = migrator.MigrateAll(); err != nil { + log.Fatalf("Failed to migrate: %v", err) + } + + contractStore := sqliteContractStore.NewSqliteContractStore(grm, l, cfg) + if err := contractStore.InitializeCoreContracts(); err != nil { + log.Fatalf("Failed to initialize core contracts: %v", err) + } + + cm := contractManager.NewContractManager(contractStore, etherscanClient, client, sdc, l) + + mds := sqliteBlockStore.NewSqliteBlockStore(grm, l, cfg) + if err != nil { + log.Fatalln(err) + } + + sm := stateManager.NewEigenStateManager(l, grm) + + if _, err := avsOperators.NewAvsOperators(sm, grm, cfg.Network, cfg.Environment, l, cfg); err != nil { + l.Sugar().Fatalw("Failed to create AvsOperatorsModel", zap.Error(err)) + } + if _, err := operatorShares.NewOperatorSharesModel(sm, grm, cfg.Network, cfg.Environment, l, cfg); err != nil { + l.Sugar().Fatalw("Failed to create OperatorSharesModel", zap.Error(err)) + } + if _, err := stakerDelegations.NewStakerDelegationsModel(sm, grm, cfg.Network, cfg.Environment, l, cfg); err != nil { + l.Sugar().Fatalw("Failed to create StakerDelegationsModel", zap.Error(err)) + } + if _, err := stakerShares.NewStakerSharesModel(sm, grm, cfg.Network, cfg.Environment, l, cfg); err != nil { + l.Sugar().Fatalw("Failed to create StakerSharesModel", zap.Error(err)) + } + + fetchr := fetcher.NewFetcher(client, cfg, l) + + idxr := indexer.NewIndexer(mds, contractStore, etherscanClient, cm, client, fetchr, l, cfg) + + p := pipeline.NewPipeline(fetchr, idxr, mds, sm, l) + + // Create new sidecar instance + sidecar := sidecar.NewSidecar(&sidecar.SidecarConfig{ + GenesisBlockNumber: cfg.GetGenesisBlockNumber(), + }, cfg, mds, p, sm, l, client) + + // RPC channel to notify the RPC server to shutdown gracefully + rpcChannel := make(chan bool) + err = sidecar.WithRpcServer(ctx, mds, sm, rpcChannel) + if err != nil { + l.Sugar().Fatalw("Failed to start RPC server", zap.Error(err)) + } + + block, err := fetchr.FetchBlock(ctx, 1215893) + if err != nil { + l.Sugar().Fatalw("Failed to fetch block", zap.Error(err)) + } + + transactionHash := "0xf6775c38af1d2802bcbc2b7c8959c0d5b48c63a14bfeda0261ba29d76c68c423" + transaction := ðereum.EthereumTransaction{} + + for _, tx := range block.Block.Transactions { + if tx.Hash.Value() == transactionHash { + transaction = tx + break + } + } + + logIndex := 4 + receipt := block.TxReceipts[transaction.Hash.Value()] + var interestingLog *ethereum.EthereumEventLog + + for _, log := range receipt.Logs { + if log.LogIndex.Value() == uint64(logIndex) { + fmt.Printf("Log: %+v\n", log) + interestingLog = log + } + } + + decodedLog, err := idxr.DecodeLogWithAbi(nil, receipt, interestingLog) + if err != nil { + l.Sugar().Fatalw("Failed to decode log", zap.Error(err)) + } + l.Sugar().Infof("Decoded log: %+v", decodedLog) +} diff --git a/go.mod b/go.mod index 2f98fef6..b9f700dd 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mattn/go-sqlite3 v1.14.23 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 3b9403fc..05724e40 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= +github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= diff --git a/internal/eigenState/stakerShares/stakerShares.go b/internal/eigenState/stakerShares/stakerShares.go index 231801db..16f4e22e 100644 --- a/internal/eigenState/stakerShares/stakerShares.go +++ b/internal/eigenState/stakerShares/stakerShares.go @@ -39,7 +39,6 @@ type AccumulatedStateChange struct { Strategy string Shares *big.Int BlockNumber uint64 - IsNegative bool } type StakerSharesDiff struct { @@ -221,20 +220,92 @@ func (ss *StakerSharesModel) handleM1StakerWithdrawals(log *storage.TransactionL return &AccumulatedStateChange{ Staker: stakerAddress, Strategy: outputData.Strategy, - Shares: shares, + Shares: shares.Mul(shares, big.NewInt(-1)), BlockNumber: log.BlockNumber, - IsNegative: true, }, nil } +type m2MigrationOutputData struct { + OldWithdrawalRoot []byte `json:"oldWithdrawalRoot"` + OldWithdrawalRootString string +} + +func parseLogOutputForM2MigrationEvent(outputDataStr string) (*m2MigrationOutputData, error) { + outputData := &m2MigrationOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + outputData.OldWithdrawalRootString = hex.EncodeToString(outputData.OldWithdrawalRoot) + return outputData, err +} + // handleMigratedM2StakerWithdrawals handles the WithdrawalMigrated event from the DelegationManager contract // // Since we have already counted M1 withdrawals due to processing events block-by-block, we need to handle not double subtracting. // Assuming that M2 WithdrawalQueued events always result in a subtraction, if we encounter a migration event, we need // to add the amount back to the shares to get the correct final state. -func (ss *StakerSharesModel) handleMigratedM2StakerWithdrawals(log *storage.TransactionLog) (*AccumulatedStateChange, error) { - // TODO(seanmcgary): come back to this... - return nil, nil +func (ss *StakerSharesModel) handleMigratedM2StakerWithdrawals(log *storage.TransactionLog) ([]*AccumulatedStateChange, error) { + outputData, err := parseLogOutputForM2MigrationEvent(log.OutputData) + if err != nil { + return nil, err + } + query := ` + with migration as ( + select + json_extract(tl.output_data, '$.nonce') as nonce, + coalesce(json_extract(tl.output_data, '$.depositor'), json_extract(tl.output_data, '$.staker')) as staker + from transaction_logs tl + where + tl.address = @strategyManagerAddress + and tl.block_number <= @logBlockNumber + and tl.event_name = 'WithdrawalQueued' + and bytes_to_hex(json_extract(tl.output_data, '$.withdrawalRoot')) = @oldWithdrawalRoot + ), + share_withdrawal_queued as ( + select + tl.*, + json_extract(tl.output_data, '$.nonce') as nonce, + coalesce(json_extract(tl.output_data, '$.depositor'), json_extract(tl.output_data, '$.staker')) as staker + from transaction_logs as tl + where + tl.address = @strategyManagerAddress + and tl.event_name = 'ShareWithdrawalQueued' + ) + select + * + from share_withdrawal_queued + where + nonce = (select nonce from migration) + and staker = (select staker from migration) + ` + logs := make([]storage.TransactionLog, 0) + res := ss.Db. + Raw(query, + sql.Named("strategyManagerAddress", ss.globalConfig.GetContractsMapForEnvAndNetwork().StrategyManager), + sql.Named("logBlockNumber", log.BlockNumber), + sql.Named("oldWithdrawalRoot", outputData.OldWithdrawalRootString), + ). + Scan(&logs) + + if res.Error != nil { + ss.logger.Sugar().Errorw("Failed to fetch share withdrawal queued logs", zap.Error(res.Error)) + return nil, res.Error + } + + changes := make([]*AccumulatedStateChange, 0) + for _, l := range logs { + c, err := ss.handleStakerDepositEvent(&l) + if err != nil { + return nil, err + } + changes = append(changes, c) + } + + return changes, nil } type m2WithdrawalOutputData struct { @@ -280,9 +351,8 @@ func (ss *StakerSharesModel) handleM2QueuedWithdrawal(log *storage.TransactionLo r := &AccumulatedStateChange{ Staker: outputData.Withdrawal.Staker, Strategy: strategy, - Shares: shares, + Shares: shares.Mul(shares, big.NewInt(-1)), BlockNumber: log.BlockNumber, - IsNegative: true, } records = append(records, r) } @@ -319,13 +389,13 @@ func (ss *StakerSharesModel) GetStateTransitions() (types.StateTransitions[Accum if err == nil { parsedRecords = append(parsedRecords, record) } - } else if log.Address == contractAddresses.DelegationManager && log.EventName == "WithdrawalMigrated" { - record, err := ss.handleMigratedM2StakerWithdrawals(log) - if err == nil { - parsedRecords = append(parsedRecords, record) - } } else if log.Address == contractAddresses.DelegationManager && log.EventName == "WithdrawalQueued" { records, err := ss.handleM2QueuedWithdrawal(log) + if err == nil && records != nil { + parsedRecords = append(parsedRecords, records...) + } + } else if log.Address == contractAddresses.DelegationManager && log.EventName == "WithdrawalMigrated" { + records, err := ss.handleMigratedM2StakerWithdrawals(log) if err == nil { parsedRecords = append(parsedRecords, records...) } @@ -348,17 +418,16 @@ func (ss *StakerSharesModel) GetStateTransitions() (types.StateTransitions[Accum } for _, parsedRecord := range parsedRecords { + if parsedRecord == nil { + continue + } slotId := NewSlotId(parsedRecord.Staker, parsedRecord.Strategy) record, ok := ss.stateAccumulator[log.BlockNumber][slotId] if !ok { record = parsedRecord ss.stateAccumulator[log.BlockNumber][slotId] = record } else { - if record.IsNegative { - record.Shares = record.Shares.Sub(record.Shares, parsedRecord.Shares) - } else { - record.Shares = record.Shares.Add(record.Shares, parsedRecord.Shares) - } + record.Shares = record.Shares.Add(record.Shares, parsedRecord.Shares) } } @@ -417,7 +486,7 @@ func (ss *StakerSharesModel) HandleStateChange(log *storage.TransactionLog) (int return nil, err } if change == nil { - return nil, xerrors.Errorf("No state change found for block %d", blockNumber) + return nil, nil } return change, nil } diff --git a/internal/eigenState/stakerShares/stakerShares_test.go b/internal/eigenState/stakerShares/stakerShares_test.go index 83067739..d17d8a16 100644 --- a/internal/eigenState/stakerShares/stakerShares_test.go +++ b/internal/eigenState/stakerShares/stakerShares_test.go @@ -39,7 +39,16 @@ func setup() ( } func teardown(model *StakerSharesModel) { - model.Db.Exec("truncate table staker_shares cascade") + queries := []string{ + `truncate table staker_shares cascade`, + `truncate table blocks cascade`, + `truncate table transactions cascade`, + `truncate table transaction_logs cascade`, + } + for _, query := range queries { + + model.Db.Raw(query) + } } func Test_StakerSharesState(t *testing.T) { @@ -121,7 +130,7 @@ func Test_StakerSharesState(t *testing.T) { typedChange := change.(*AccumulatedStateChanges) assert.Equal(t, 1, len(typedChange.Changes)) - expectedShares, _ := numbers.NewBig257().SetString("246393621132195985", 10) + expectedShares, _ := numbers.NewBig257().SetString("-246393621132195985", 10) assert.Equal(t, expectedShares, typedChange.Changes[0].Shares) assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Changes[0].Staker) assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Changes[0].Strategy) @@ -193,11 +202,317 @@ func Test_StakerSharesState(t *testing.T) { typedChange := change.(*AccumulatedStateChanges) assert.Equal(t, 1, len(typedChange.Changes)) - expectedShares, _ := numbers.NewBig257().SetString("1000000000000000000", 10) + expectedShares, _ := numbers.NewBig257().SetString("-1000000000000000000", 10) assert.Equal(t, expectedShares, typedChange.Changes[0].Shares) assert.Equal(t, strings.ToLower("0x3c42cd72639e3e8d11ab8d0072cc13bd5d8aa83c"), typedChange.Changes[0].Staker) assert.Equal(t, "0xd523267698c81a372191136e477fdebfa33d9fb4", typedChange.Changes[0].Strategy) + teardown(model) + }) + t.Run("Should capture M2 migration", func(t *testing.T) { + t.Skip() + esm := stateManager.NewEigenStateManager(l, grm) + + originBlockNumber := uint64(100) + + block := storage.Block{ + Number: originBlockNumber, + Hash: "some hash", + } + res := grm.Model(storage.Block{}).Create(&block) + if res.Error != nil { + t.Fatal(res.Error) + } + + transaction := storage.Transaction{ + BlockNumber: block.Number, + TransactionHash: "0x5ff283cb420cdf950036d538e2223d5b504b875828f6e0d243002f429da6faa2", + TransactionIndex: big.NewInt(200).Uint64(), + FromAddress: "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", + } + res = grm.Model(storage.Transaction{}).Create(&transaction) + if res.Error != nil { + t.Fatal(res.Error) + } + + // setup M1 withdrawal WithdrawalQueued (has root) and N many ShareWithdrawalQueued events (staker, strategy, shares) + shareWithdrawalQueued := storage.TransactionLog{ + TransactionHash: "0x5ff283cb420cdf950036d538e2223d5b504b875828f6e0d243002f429da6faa2", + TransactionIndex: big.NewInt(200).Uint64(), + BlockNumber: originBlockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().StrategyManager, + Arguments: `[{"Name": "depositor", "Type": "address", "Value": null, "Indexed": false}, {"Name": "nonce", "Type": "uint96", "Value": null, "Indexed": false}, {"Name": "strategy", "Type": "address", "Value": null, "Indexed": false}, {"Name": "shares", "Type": "uint256", "Value": null, "Indexed": false}]`, + EventName: "ShareWithdrawalQueued", + LogIndex: big.NewInt(1).Uint64(), + OutputData: `{"nonce": 0, "shares": 246393621132195985, "strategy": "0x298afb19a105d59e74658c4c334ff360bade6dd2", "depositor": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78"}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + res = grm.Model(storage.TransactionLog{}).Create(&shareWithdrawalQueued) + if res.Error != nil { + t.Fatal(res.Error) + } + + withdrawalQueued := storage.TransactionLog{ + TransactionHash: "0x5ff283cb420cdf950036d538e2223d5b504b875828f6e0d243002f429da6faa2", + TransactionIndex: big.NewInt(200).Uint64(), + BlockNumber: originBlockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().StrategyManager, + Arguments: `[{"Name": "depositor", "Type": "address", "Value": null, "Indexed": false}, {"Name": "nonce", "Type": "uint96", "Value": null, "Indexed": false}, {"Name": "withdrawer", "Type": "address", "Value": null, "Indexed": false}, {"Name": "delegatedAddress", "Type": "address", "Value": null, "Indexed": false}, {"Name": "withdrawalRoot", "Type": "bytes32", "Value": null, "Indexed": false}]`, + EventName: "WithdrawalQueued", + LogIndex: big.NewInt(2).Uint64(), + OutputData: `{"nonce": 0, "depositor": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", "withdrawer": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", "withdrawalRoot": [31, 200, 156, 159, 43, 41, 112, 204, 139, 225, 142, 72, 58, 63, 194, 149, 59, 254, 218, 227, 162, 25, 237, 7, 103, 240, 24, 255, 31, 152, 236, 84], "delegatedAddress": "0x0000000000000000000000000000000000000000"}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + res = grm.Model(storage.TransactionLog{}).Create(&withdrawalQueued) + if res.Error != nil { + t.Fatal(res.Error) + } + + blockNumber := uint64(200) + log := storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(300).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().DelegationManager, + Arguments: `[{"Name": "oldWithdrawalRoot", "Type": "bytes32", "Value": ""}, {"Name": "newWithdrawalRoot", "Type": "bytes32", "Value": ""}]`, + EventName: "WithdrawalMigrated", + LogIndex: big.NewInt(600).Uint64(), + OutputData: `{"newWithdrawalRoot": [218, 200, 138, 86, 38, 9, 156, 119, 73, 13, 168, 40, 209, 43, 238, 83, 234, 177, 230, 73, 120, 205, 255, 143, 255, 216, 51, 209, 137, 100, 163, 233], "oldWithdrawalRoot": [31, 200, 156, 159, 43, 41, 112, 204, 139, 225, 142, 72, 58, 63, 194, 149, 59, 254, 218, 227, 162, 25, 237, 7, 103, 240, 24, 255, 31, 152, 236, 84]}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + + model, err := NewStakerSharesModel(esm, grm, cfg.Network, cfg.Environment, l, cfg) + + err = model.InitBlockProcessing(blockNumber) + assert.Nil(t, err) + + change, err := model.HandleStateChange(&log) + assert.Nil(t, err) + assert.NotNil(t, change) + + typedChange := change.(*AccumulatedStateChanges) + assert.Equal(t, 1, len(typedChange.Changes)) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Changes[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Changes[0].Strategy) + assert.Equal(t, "246393621132195985", typedChange.Changes[0].Shares.String()) + + preparedChange, err := model.prepareState(blockNumber) + assert.Nil(t, err) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", preparedChange[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", preparedChange[0].Strategy) + assert.Equal(t, "246393621132195985", preparedChange[0].Shares.String()) + + err = model.clonePreviousBlocksToNewBlock(blockNumber) + assert.Nil(t, err) + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + query := `select * from staker_shares where block_number = ?` + results := []*StakerShares{} + res = model.Db.Raw(query, blockNumber).Scan(&results) + assert.Nil(t, res.Error) + assert.Equal(t, 1, len(results)) + + teardown(model) + }) + t.Run("Should handle an M1 withdrawal and migration to M2 correctly", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + model, err := NewStakerSharesModel(esm, grm, cfg.Network, cfg.Environment, l, cfg) + assert.Nil(t, err) + + originBlockNumber := uint64(101) + originTxHash := "0x5ff283cb420cdf950036d538e2223d5b504b875828f6e0d243002f429da6faa3" + + block := storage.Block{ + Number: originBlockNumber, + Hash: "some hash", + } + res := grm.Model(storage.Block{}).Create(&block) + if res.Error != nil { + t.Fatal(res.Error) + } + + transaction := storage.Transaction{ + BlockNumber: block.Number, + TransactionHash: originTxHash, + TransactionIndex: big.NewInt(200).Uint64(), + FromAddress: "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", + } + res = grm.Model(storage.Transaction{}).Create(&transaction) + if res.Error != nil { + t.Fatal(res.Error) + } + + // Insert the M1 withdrawal since we'll need it later + shareWithdrawalQueued := storage.TransactionLog{ + TransactionHash: originTxHash, + TransactionIndex: big.NewInt(1).Uint64(), + BlockNumber: originBlockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().StrategyManager, + Arguments: `[{"Name": "depositor", "Type": "address", "Value": null, "Indexed": false}, {"Name": "nonce", "Type": "uint96", "Value": null, "Indexed": false}, {"Name": "strategy", "Type": "address", "Value": null, "Indexed": false}, {"Name": "shares", "Type": "uint256", "Value": null, "Indexed": false}]`, + EventName: "ShareWithdrawalQueued", + LogIndex: big.NewInt(1).Uint64(), + OutputData: `{"nonce": 0, "shares": 246393621132195985, "strategy": "0x298afb19a105d59e74658c4c334ff360bade6dd2", "depositor": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78"}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + res = grm.Model(storage.TransactionLog{}).Create(&shareWithdrawalQueued) + if res.Error != nil { + t.Fatal(res.Error) + } + + // init processing for the M1 withdrawal + err = model.InitBlockProcessing(originBlockNumber) + assert.Nil(t, err) + + change, err := model.HandleStateChange(&shareWithdrawalQueued) + assert.Nil(t, err) + assert.NotNil(t, change) + + typedChange := change.(*AccumulatedStateChanges) + + assert.Equal(t, 1, len(typedChange.Changes)) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Changes[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Changes[0].Strategy) + assert.Equal(t, "-246393621132195985", typedChange.Changes[0].Shares.String()) + + slotId := NewSlotId(typedChange.Changes[0].Staker, typedChange.Changes[0].Strategy) + + accumulatedState, ok := model.stateAccumulator[originBlockNumber][slotId] + assert.True(t, ok) + assert.NotNil(t, accumulatedState) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", accumulatedState.Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", accumulatedState.Strategy) + assert.Equal(t, "-246393621132195985", accumulatedState.Shares.String()) + + // Insert the other half of the M1 event that captures the withdrawalRoot associated with the M1 withdrawal + // No need to process this event, we just need it to be present in the DB + withdrawalQueued := storage.TransactionLog{ + TransactionHash: originTxHash, + TransactionIndex: big.NewInt(200).Uint64(), + BlockNumber: originBlockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().StrategyManager, + Arguments: `[{"Name": "depositor", "Type": "address", "Value": null, "Indexed": false}, {"Name": "nonce", "Type": "uint96", "Value": null, "Indexed": false}, {"Name": "withdrawer", "Type": "address", "Value": null, "Indexed": false}, {"Name": "delegatedAddress", "Type": "address", "Value": null, "Indexed": false}, {"Name": "withdrawalRoot", "Type": "bytes32", "Value": null, "Indexed": false}]`, + EventName: "WithdrawalQueued", + LogIndex: big.NewInt(2).Uint64(), + OutputData: `{"nonce": 0, "depositor": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", "withdrawer": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", "withdrawalRoot": [31, 200, 156, 159, 43, 41, 112, 204, 139, 225, 142, 72, 58, 63, 194, 149, 59, 254, 218, 227, 162, 25, 237, 7, 103, 240, 24, 255, 31, 152, 236, 84], "delegatedAddress": "0x0000000000000000000000000000000000000000"}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + res = grm.Model(storage.TransactionLog{}).Create(&withdrawalQueued) + if res.Error != nil { + t.Fatal(res.Error) + } + + change, err = model.HandleStateChange(&withdrawalQueued) + assert.Nil(t, err) + assert.Nil(t, change) // should be nil since the handler doesnt care about this event + + err = model.CommitFinalState(originBlockNumber) + assert.Nil(t, err) + + // verify the M1 withdrawal was processed correctly + query := `select * from staker_shares where block_number = ?` + results := []*StakerShares{} + res = model.Db.Raw(query, originBlockNumber).Scan(&results) + + assert.Nil(t, res.Error) + assert.Equal(t, 1, len(results)) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", results[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", results[0].Strategy) + assert.Equal(t, "-246393621132195985", results[0].Shares) + + // setup M2 migration + blockNumber := uint64(102) + err = model.InitBlockProcessing(blockNumber) + assert.Nil(t, err) + + // M2 WithdrawalQueued comes before the M2 WithdrawalMigrated event + log := storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(1).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().DelegationManager, + Arguments: `[{"Name": "withdrawalRoot", "Type": "bytes32", "Value": ""}, {"Name": "withdrawal", "Type": "(address,address,address,uint256,uint32,address[],uint256[])", "Value": ""}]`, + EventName: "WithdrawalQueued", + LogIndex: big.NewInt(600).Uint64(), + OutputData: `{"withdrawal": {"nonce": 0, "shares": [246393621132195985], "staker": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", "startBlock": 1215690, "strategies": ["0x298afb19a105d59e74658c4c334ff360bade6dd2"], "withdrawer": "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", "delegatedTo": "0x2177dee1f66d6dbfbf517d9c4f316024c6a21aeb"}, "withdrawalRoot": [24, 23, 49, 137, 14, 63, 119, 12, 234, 225, 63, 35, 109, 249, 112, 24, 241, 118, 212, 52, 22, 107, 202, 56, 105, 37, 68, 47, 169, 23, 142, 135]}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + + change, err = model.HandleStateChange(&log) + assert.Nil(t, err) + assert.NotNil(t, change) + + typedChange = change.(*AccumulatedStateChanges) + assert.Equal(t, 1, len(typedChange.Changes)) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Changes[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Changes[0].Strategy) + assert.Equal(t, "-246393621132195985", typedChange.Changes[0].Shares.String()) + + // M2 WithdrawalMigrated event. Typically occurs in the same block as the M2 WithdrawalQueued event + withdrawalMigratedLog := storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(2).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().DelegationManager, + Arguments: `[{"Name": "oldWithdrawalRoot", "Type": "bytes32", "Value": ""}, {"Name": "newWithdrawalRoot", "Type": "bytes32", "Value": ""}]`, + EventName: "WithdrawalMigrated", + LogIndex: big.NewInt(600).Uint64(), + OutputData: `{"newWithdrawalRoot": [24, 23, 49, 137, 14, 63, 119, 12, 234, 225, 63, 35, 109, 249, 112, 24, 241, 118, 212, 52, 22, 107, 202, 56, 105, 37, 68, 47, 169, 23, 142, 135], "oldWithdrawalRoot": [31, 200, 156, 159, 43, 41, 112, 204, 139, 225, 142, 72, 58, 63, 194, 149, 59, 254, 218, 227, 162, 25, 237, 7, 103, 240, 24, 255, 31, 152, 236, 84]}`, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + + change, err = model.HandleStateChange(&withdrawalMigratedLog) + assert.Nil(t, err) + assert.NotNil(t, change) + + typedChange = change.(*AccumulatedStateChanges) + assert.Equal(t, 1, len(typedChange.Changes)) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", typedChange.Changes[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", typedChange.Changes[0].Strategy) + assert.Equal(t, "246393621132195985", typedChange.Changes[0].Shares.String()) + + slotId = NewSlotId(typedChange.Changes[0].Staker, typedChange.Changes[0].Strategy) + + accumulatedState, ok = model.stateAccumulator[blockNumber][slotId] + assert.True(t, ok) + assert.NotNil(t, accumulatedState) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", accumulatedState.Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", accumulatedState.Strategy) + assert.Equal(t, "0", accumulatedState.Shares.String()) + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + // Get the state at the new block and verify the shares amount is correct + query = ` + select * from staker_shares + where block_number = ? + ` + results = []*StakerShares{} + res = model.Db.Raw(query, blockNumber).Scan(&results) + assert.Nil(t, res.Error) + + assert.Equal(t, 1, len(results)) + assert.Equal(t, "0x9c01148c464cf06d135ad35d3d633ab4b46b9b78", results[0].Staker) + assert.Equal(t, "0x298afb19a105d59e74658c4c334ff360bade6dd2", results[0].Strategy) + assert.Equal(t, "-246393621132195985", results[0].Shares) + assert.Equal(t, blockNumber, results[0].BlockNumber) + teardown(model) }) } diff --git a/internal/indexer/transactionLogs.go b/internal/indexer/transactionLogs.go index 56024fc6..60a1c15b 100644 --- a/internal/indexer/transactionLogs.go +++ b/internal/indexer/transactionLogs.go @@ -236,7 +236,7 @@ func (idx *Indexer) DecodeLogWithAbi( // If the address of the log is not the same as the contract address, we need to load the ABI for the log // // The typical case is when a contract interacts with another contract that emits an event - if utils.AreAddressesEqual(logAddress.String(), txReceipt.GetTargetAddress().Value()) { + if utils.AreAddressesEqual(logAddress.String(), txReceipt.GetTargetAddress().Value()) && a != nil { return idx.DecodeLog(a, lg) } else { idx.Logger.Sugar().Debugw("Log address does not match contract address", zap.String("logAddress", logAddress.String()), zap.String("contractAddress", txReceipt.GetTargetAddress().Value())) From 336f218ff12529522eb8cb8149728bddba200f4f Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Tue, 10 Sep 2024 10:30:09 -0500 Subject: [PATCH 5/6] Fix bytes_to_hex test --- internal/sqlite/sqlite_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/sqlite/sqlite_test.go b/internal/sqlite/sqlite_test.go index cd8d6588..bcf4eddb 100644 --- a/internal/sqlite/sqlite_test.go +++ b/internal/sqlite/sqlite_test.go @@ -1,7 +1,9 @@ package sqlite import ( + "encoding/hex" "github.com/stretchr/testify/assert" + "strings" "testing" ) @@ -37,7 +39,10 @@ func Test_Sqlite(t *testing.T) { hexValue := &results{} res := grm.Raw(query).Scan(&hexValue) + + expectedBytes := []byte{218, 200, 138, 86, 38, 9, 156, 119, 73, 13, 168, 40, 209, 43, 238, 83, 234, 177, 230, 73, 120, 205, 255, 143, 255, 216, 51, 209, 137, 100, 163, 233} + assert.Nil(t, res.Error) - assert.Equal(t, "dac88a5626099c77490da828d12be653eab1e64978cdff8fffD337d18964a3e9", hexValue.WithdrawalHex) + assert.Equal(t, strings.ToLower(hex.EncodeToString(expectedBytes)), hexValue.WithdrawalHex) }) } From 8277da93360d15594436dded7da3159bfeaa9eb4 Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Tue, 10 Sep 2024 10:44:01 -0500 Subject: [PATCH 6/6] Add some comments --- internal/sqlite/sqlite.go | 4 ++++ internal/sqlite/sqlite_test.go | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/internal/sqlite/sqlite.go b/internal/sqlite/sqlite.go index 0cc1dcdb..45c0ef85 100644 --- a/internal/sqlite/sqlite.go +++ b/internal/sqlite/sqlite.go @@ -11,6 +11,10 @@ import ( "gorm.io/gorm/logger" ) +// bytesToHex is a custom SQLite function that converts a JSON byte array to a hex string. +// +// @param jsonByteArray: a JSON byte array, e.g. [1, 2, 3, ...] +// @return: a hex string without a leading 0x, e.g. 78cc56f0700e7ba5055f12... func bytesToHex(jsonByteArray string) (string, error) { jsonBytes := make([]byte, 0) err := json.Unmarshal([]byte(jsonByteArray), &jsonBytes) diff --git a/internal/sqlite/sqlite_test.go b/internal/sqlite/sqlite_test.go index bcf4eddb..736b02f9 100644 --- a/internal/sqlite/sqlite_test.go +++ b/internal/sqlite/sqlite_test.go @@ -8,15 +8,6 @@ import ( ) func Test_Sqlite(t *testing.T) { - // t.Run("Should create a new GormSqlite", func(t *testing.T) { - // s := NewSqlite("file::memory:?cache=shared") - // grm, err := NewGormSqliteFromSqlite(s) - // assert.Nil(t, err) - // assert.NotNil(t, grm) - // - // db, err := grm.DB() - // defer db.Close() - // }) t.Run("Should use the bytesToHex function", func(t *testing.T) { query := ` with json_values as (