diff --git a/pkg/operator/allocations/show.go b/pkg/operator/allocations/show.go index 11d6d5b1..15184bca 100644 --- a/pkg/operator/allocations/show.go +++ b/pkg/operator/allocations/show.go @@ -2,9 +2,11 @@ package allocations import ( "context" + "errors" "fmt" "math/big" "sort" + "strings" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" @@ -22,11 +24,6 @@ import ( "github.com/urfave/cli/v2" ) -var ( - // PrecisionFactor comes from the allocation manager contract - PrecisionFactor = big.NewInt(1e18) -) - func ShowCmd(p utils.Prompter) *cli.Command { showCmd := &cli.Command{ Name: "show", @@ -197,7 +194,18 @@ func showAction(cCtx *cli.Context, p utils.Prompter) error { if config.outputType == string(common.OutputType_Json) { slashableMagnitudeHolders.PrintJSON() } else { - slashableMagnitudeHolders.PrintPretty() + if !common.IsEmptyString(config.output) { + if !strings.HasSuffix(config.output, ".csv") { + return errors.New("output file must be a .csv file") + } + err = slashableMagnitudeHolders.WriteToCSV(config.output) + if err != nil { + return err + } + logger.Infof("Allocation state written to file: %s", config.output) + } else { + slashableMagnitudeHolders.PrintPretty() + } } if len(dergisteredOpsets) > 0 { diff --git a/pkg/operator/allocations/types.go b/pkg/operator/allocations/types.go index e6bbd1fb..b5a9104f 100644 --- a/pkg/operator/allocations/types.go +++ b/pkg/operator/allocations/types.go @@ -1,9 +1,12 @@ package allocations import ( + "encoding/csv" "encoding/json" "fmt" "math/big" + "os" + "reflect" "strings" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -139,16 +142,73 @@ type showConfig struct { type SlashableMagnitudeHolders []SlashableMagnitudesHolder type SlashableMagnitudesHolder struct { - StrategyAddress gethcommon.Address - AVSAddress gethcommon.Address - OperatorSetId uint32 - SlashableMagnitude uint64 - NewMagnitude uint64 - NewAllocationShares *big.Int - UpdateBlock uint32 - Shares *big.Int - SharesPercentage string - UpcomingSharesPercentage string + StrategyAddress gethcommon.Address `csv:"strategy_address"` + AVSAddress gethcommon.Address `csv:"avs_address"` + OperatorSetId uint32 `csv:"operator_set_id"` + SlashableMagnitude uint64 `csv:"-"` + NewMagnitude uint64 `csv:"-"` + Shares *big.Int `csv:"shares"` + SharesPercentage string `csv:"shares_percentage"` + NewAllocationShares *big.Int `csv:"new_allocation_shares"` + UpcomingSharesPercentage string `csv:"upcoming_shares_percentage"` + UpdateBlock uint32 `csv:"update_block"` +} + +func (s SlashableMagnitudeHolders) WriteToCSV(filePath string) error { + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("failed to create file: %v", err) + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + + // Get fields and their CSV names, excluding skipped fields + var headers []string + var fieldIndices []int + val := reflect.ValueOf(SlashableMagnitudesHolder{}) + typ := val.Type() + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + csvTag := field.Tag.Get("csv") + + // Skip if tag is "-" + if csvTag == "-" { + continue + } + + // Use tag value if present, otherwise use field name + if csvTag != "" { + headers = append(headers, csvTag) + } else { + headers = append(headers, field.Name) + } + fieldIndices = append(fieldIndices, i) + } + + // Write headers + if err := writer.Write(headers); err != nil { + return fmt.Errorf("failed to write headers: %v", err) + } + + // Write data rows + for _, eachRow := range s { + val := reflect.ValueOf(eachRow) + row := make([]string, len(fieldIndices)) + // Only include non-skipped fields + for i, fieldIndex := range fieldIndices { + field := val.Field(fieldIndex) + row[i] = fmt.Sprintf("%v", field.Interface()) + } + + if err := writer.Write(row); err != nil { + return fmt.Errorf("failed to write row: %v", err) + } + } + + return nil } func (s SlashableMagnitudeHolders) PrintPretty() {