From 4c2227641d2575be6dde07c4f3beacf117f6bbc5 Mon Sep 17 00:00:00 2001 From: gpabst Date: Tue, 3 Dec 2024 17:03:55 -0500 Subject: [PATCH 1/4] feat: Rewards v2 batch claiming support (#241) --- go.mod | 2 +- go.sum | 2 + pkg/internal/common/flags/general.go | 7 + pkg/rewards/claim.go | 258 +++++++++++++++++++++------ pkg/rewards/types.go | 1 + samples/batch-claims.yaml | 7 + 6 files changed, 219 insertions(+), 58 deletions(-) create mode 100644 samples/batch-claims.yaml diff --git a/go.mod b/go.mod index df6f6200..cbdc0230 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - github.com/Layr-Labs/eigensdk-go v0.1.13-0.20241023200243-565bb4438918 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241121204729-7d2cd162ffe8 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index 1784a9a8..f893523b 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248- github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= github.com/Layr-Labs/eigensdk-go v0.1.13-0.20241023200243-565bb4438918 h1:Itl141PoMFzq58ZTo4Nu/CyH+x8f4BH6OmBNhZ6Z2/I= github.com/Layr-Labs/eigensdk-go v0.1.13-0.20241023200243-565bb4438918/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241121204729-7d2cd162ffe8 h1:6wuVq+Elto+yF7bQ3QYqD2psxGXR3wcJh2koNcUjIQM= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241121204729-7d2cd162ffe8/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/internal/common/flags/general.go b/pkg/internal/common/flags/general.go index 194421ce..2ae9378c 100644 --- a/pkg/internal/common/flags/general.go +++ b/pkg/internal/common/flags/general.go @@ -90,4 +90,11 @@ var ( EnvVars: []string{"EXPIRY"}, Value: 3600, } + + BatchClaimFile = cli.StringFlag{ + Name: "batch-claim-file", + Aliases: []string{"bcf"}, + Usage: "Input file for batch rewards claim", + EnvVars: []string{"BATCH_CLAIM_FILE"}, + } ) diff --git a/pkg/rewards/claim.go b/pkg/rewards/claim.go index 6a825bcd..fb078fca 100644 --- a/pkg/rewards/claim.go +++ b/pkg/rewards/claim.go @@ -7,6 +7,7 @@ import ( "fmt" "math/big" "net/http" + "os" "sort" "strings" "time" @@ -15,11 +16,14 @@ import ( "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/wealdtech/go-merkletree/v2" + "gopkg.in/yaml.v2" contractrewardscoordinator "github.com/Layr-Labs/eigenlayer-contracts/pkg/bindings/IRewardsCoordinator" "github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/claimgen" "github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/distribution" + "github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/proofDataFetcher" "github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/proofDataFetcher/httpProofDataFetcher" "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" @@ -28,6 +32,7 @@ import ( eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/urfave/cli/v2" @@ -75,6 +80,7 @@ func getClaimFlags() []cli.Flag { &ProofStoreBaseURLFlag, &flags.VerboseFlag, &flags.SilentFlag, + &flags.BatchClaimFile, } allFlags := append(baseFlags, flags.GetSignerFlags()...) @@ -82,6 +88,131 @@ func getClaimFlags() []cli.Flag { return allFlags } +func batchClaim( + ctx context.Context, + logger logging.Logger, + ethClient *ethclient.Client, + elReader *elcontracts.ChainReader, + config *ClaimConfig, + p utils.Prompter, + rootIndex uint32, + proofData *proofDataFetcher.RewardProofData, +) error { + + yamlFile, err := os.ReadFile(config.BatchClaimFile) + if err != nil { + return eigenSdkUtils.WrapError("failed to read YAML config file", err) + } + + var claimConfigs []struct { + EarnerAddress string `yaml:"earner_address"` + TokenAddresses []string `yaml:"token_addresses"` + } + + err = yaml.Unmarshal(yamlFile, &claimConfigs) + if err != nil { + return eigenSdkUtils.WrapError("failed to parse YAML config", err) + } + + var elClaims []rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim + var claims []contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim + var accounts []merkletree.MerkleTree + + for _, claimConfig := range claimConfigs { + earnerAddr := gethcommon.HexToAddress(claimConfig.EarnerAddress) + + var tokenAddrs []gethcommon.Address + + // Empty token addresses list will create a claim for all tokens claimable + // by the earner address. + if len(claimConfig.TokenAddresses) != 0 { + for _, addr := range claimConfig.TokenAddresses { + tokenAddrs = append(tokenAddrs, gethcommon.HexToAddress(addr)) + } + } + + elClaim, claim, account, err := generateClaimPayload( + ctx, + rootIndex, + proofData, + elReader, + logger, + earnerAddr, + tokenAddrs, + ) + + if err != nil { + logger.Warnf("Failed to process claim for earner %s: %v", earnerAddr.String(), err) + continue + } + + elClaims = append(elClaims, *elClaim) + claims = append(claims, *claim) + accounts = append(accounts, *account) + + } + + return broadcastClaims(config, ethClient, logger, p, ctx, elClaims, claims, accounts) +} + +func generateClaimPayload( + ctx context.Context, + rootIndex uint32, + proofData *proofDataFetcher.RewardProofData, + elReader *elcontracts.ChainReader, + logger logging.Logger, + earnerAddress gethcommon.Address, + tokenAddresses []gethcommon.Address, +) (*rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, *contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, *merkletree.MerkleTree, error) { + + claimableTokensOrderMap, present := proofData.Distribution.GetTokensForEarner(earnerAddress) + if !present { + return nil, nil, nil, errors.New("no tokens claimable by earner") + } + + claimableTokensMap := getTokensToClaim(claimableTokensOrderMap, tokenAddresses) + + claimableTokens, err := filterClaimableTokens(ctx, elReader, earnerAddress, claimableTokensMap) + if err != nil { + return nil, nil, nil, eigenSdkUtils.WrapError("failed to get claimable tokens", err) + } + + cg := claimgen.NewClaimgen(proofData.Distribution) + accounts, claim, err := cg.GenerateClaimProofForEarner( + earnerAddress, + claimableTokens, + rootIndex, + ) + if err != nil { + return nil, nil, nil, eigenSdkUtils.WrapError("failed to generate claim proof for earner", err) + } + + elClaim := rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{ + RootIndex: claim.RootIndex, + EarnerIndex: claim.EarnerIndex, + EarnerTreeProof: claim.EarnerTreeProof, + EarnerLeaf: rewardscoordinator.IRewardsCoordinatorEarnerTreeMerkleLeaf{ + Earner: claim.EarnerLeaf.Earner, + EarnerTokenRoot: claim.EarnerLeaf.EarnerTokenRoot, + }, + TokenIndices: claim.TokenIndices, + TokenTreeProofs: claim.TokenTreeProofs, + TokenLeaves: convertClaimTokenLeaves(claim.TokenLeaves), + } + + logger.Infof("Validating claim proof for earner %s...", earnerAddress) + ok, err := elReader.CheckClaim(ctx, elClaim) + if err != nil { + return nil, nil, nil, err + } + if !ok { + return nil, nil, nil, errors.New("failed to validate claim") + } + logger.Infof("Claim proof for earner %s validated successfully", earnerAddress) + + return &elClaim, claim, accounts, nil +} + func Claim(cCtx *cli.Context, p utils.Prompter) error { ctx := cCtx.Context logger := common.GetLogger(cCtx) @@ -90,6 +221,7 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { if err != nil { return eigenSdkUtils.WrapError("failed to read and validate claim config", err) } + cCtx.App.Metadata["network"] = config.ChainID.String() ethClient, err := ethclient.Dial(config.RPCUrl) @@ -101,7 +233,8 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { elcontracts.Config{ RewardsCoordinatorAddress: config.RewardsCoordinatorAddress, }, - ethClient, logger, + ethClient, + logger, ) if err != nil { return eigenSdkUtils.WrapError("failed to create new reader from config", err) @@ -124,51 +257,45 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { return eigenSdkUtils.WrapError("failed to fetch claim amounts for date", err) } - claimableTokensOrderMap, present := proofData.Distribution.GetTokensForEarner(config.EarnerAddress) - if !present { - return errors.New("no tokens claimable by earner") - } - - claimableTokensMap := getTokensToClaim(claimableTokensOrderMap, config.TokenAddresses) - - claimableTokens, err := filterClaimableTokens(ctx, elReader, config.EarnerAddress, claimableTokensMap) - if err != nil { - return eigenSdkUtils.WrapError("failed to get claimable tokens", err) + if config.BatchClaimFile != "" { + return batchClaim(ctx, logger, ethClient, elReader, config, p, rootIndex, proofData) } - cg := claimgen.NewClaimgen(proofData.Distribution) - accounts, claim, err := cg.GenerateClaimProofForEarner( - config.EarnerAddress, - claimableTokens, + elClaim, claim, account, err := generateClaimPayload( + ctx, rootIndex, + proofData, + elReader, + logger, + config.EarnerAddress, + config.TokenAddresses, ) - if err != nil { - return eigenSdkUtils.WrapError("failed to generate claim proof for earner", err) - } - - elClaim := rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{ - RootIndex: claim.RootIndex, - EarnerIndex: claim.EarnerIndex, - EarnerTreeProof: claim.EarnerTreeProof, - EarnerLeaf: rewardscoordinator.IRewardsCoordinatorEarnerTreeMerkleLeaf{ - Earner: claim.EarnerLeaf.Earner, - EarnerTokenRoot: claim.EarnerLeaf.EarnerTokenRoot, - }, - TokenIndices: claim.TokenIndices, - TokenTreeProofs: claim.TokenTreeProofs, - TokenLeaves: convertClaimTokenLeaves(claim.TokenLeaves), - } - logger.Info("Validating claim proof...") - ok, err := elReader.CheckClaim(ctx, elClaim) if err != nil { return err } - if !ok { - return errors.New("failed to validate claim") - } - logger.Info("Claim proof validated successfully") + elClaims := []rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{*elClaim} + claims := []contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{*claim} + accounts := []merkletree.MerkleTree{*account} + err = broadcastClaims(config, ethClient, logger, p, ctx, elClaims, claims, accounts) + + return err +} + +func broadcastClaims( + config *ClaimConfig, + ethClient *ethclient.Client, + logger logging.Logger, + p utils.Prompter, + ctx context.Context, + elClaims []rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, + claims []contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, + accounts []merkletree.MerkleTree, +) error { + if len(elClaims) == 0 { + return fmt.Errorf("at least one claim is required") + } if config.Broadcast { eLWriter, err := common.GetELWriter( config.ClaimerAddress, @@ -187,7 +314,15 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { } logger.Infof("Broadcasting claim transaction...") - receipt, err := eLWriter.ProcessClaim(ctx, elClaim, config.RecipientAddress, true) + + var receipt *types.Receipt + + if len(elClaims) > 1 { + receipt, err = eLWriter.ProcessClaims(ctx, elClaims, config.RecipientAddress, true) + } else { + receipt, err = eLWriter.ProcessClaim(ctx, elClaims[0], config.RecipientAddress, true) + } + if err != nil { return eigenSdkUtils.WrapError("failed to process claim", err) } @@ -215,8 +350,12 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { // Claimer is a smart contract noSendTxOpts.GasLimit = 150_000 } - - unsignedTx, err := contractBindings.RewardsCoordinator.ProcessClaim(noSendTxOpts, elClaim, config.RecipientAddress) + var unsignedTx *types.Transaction + if len(elClaims) > 1 { + unsignedTx, err = contractBindings.RewardsCoordinator.ProcessClaims(noSendTxOpts, elClaims, config.RecipientAddress) + } else { + unsignedTx, err = contractBindings.RewardsCoordinator.ProcessClaim(noSendTxOpts, elClaims[0], config.RecipientAddress) + } if err != nil { return eigenSdkUtils.WrapError("failed to create unsigned tx", err) } @@ -234,33 +373,36 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { fmt.Println(calldataHex) } } else if config.OutputType == string(common.OutputType_Json) { - solidityClaim := claimgen.FormatProofForSolidity(accounts.Root(), claim) - jsonData, err := json.MarshalIndent(solidityClaim, "", " ") - if err != nil { - logger.Error("Error marshaling JSON:", err) - return err - } - if !common.IsEmptyString(config.Output) { - err = common.WriteToFile(jsonData, config.Output) + for idx, claim := range claims { + solidityClaim := claimgen.FormatProofForSolidity(accounts[idx].Root(), &claim) + jsonData, err := json.MarshalIndent(solidityClaim, "", " ") if err != nil { return err } - logger.Infof("Claim written to file: %s", config.Output) - } else { - fmt.Println(string(jsonData)) - fmt.Println() - fmt.Println("To write to a file, use the --output flag") + if !common.IsEmptyString(config.Output) { + err = common.WriteToFile(jsonData, config.Output) + if err != nil { + return err + } + logger.Infof("Claim written to file: %s", config.Output) + } else { + fmt.Println(string(jsonData)) + fmt.Println() + fmt.Println("To write to a file, use the --output flag") + } } } else { if !common.IsEmptyString(config.Output) { fmt.Println("output file not supported for pretty output type") fmt.Println() } - solidityClaim := claimgen.FormatProofForSolidity(accounts.Root(), claim) - if !config.IsSilent { - fmt.Println("------- Claim generated -------") + for idx, claim := range claims { + solidityClaim := claimgen.FormatProofForSolidity(accounts[idx].Root(), &claim) + if !config.IsSilent { + fmt.Println("------- Claim generated -------") + } + common.PrettyPrintStruct(*solidityClaim) } - common.PrettyPrintStruct(*solidityClaim) if !config.IsSilent { fmt.Println("-------------------------------") fmt.Println("To write to a file, use the --output flag") @@ -406,6 +548,7 @@ func readAndValidateClaimConfig(cCtx *cli.Context, logger logging.Logger) (*Clai validTokenAddresses := getValidHexAddresses(splitTokenAddresses) rewardsCoordinatorAddress := cCtx.String(RewardsCoordinatorAddressFlag.Name) isSilent := cCtx.Bool(flags.SilentFlag.Name) + batchClaimFile := cCtx.String(flags.BatchClaimFile.Name) var err error if common.IsEmptyString(rewardsCoordinatorAddress) { @@ -490,6 +633,7 @@ func readAndValidateClaimConfig(cCtx *cli.Context, logger logging.Logger) (*Clai ClaimTimestamp: claimTimestamp, ClaimerAddress: claimerAddress, IsSilent: isSilent, + BatchClaimFile: batchClaimFile, }, nil } diff --git a/pkg/rewards/types.go b/pkg/rewards/types.go index 6f020515..50e88ea6 100644 --- a/pkg/rewards/types.go +++ b/pkg/rewards/types.go @@ -32,6 +32,7 @@ type ClaimConfig struct { Environment string SignerConfig *types.SignerConfig IsSilent bool + BatchClaimFile string } type SetClaimerConfig struct { diff --git a/samples/batch-claims.yaml b/samples/batch-claims.yaml new file mode 100644 index 00000000..3aa78308 --- /dev/null +++ b/samples/batch-claims.yaml @@ -0,0 +1,7 @@ +- earner_address: "0x025246421e7247a729bbcff652c5cc1815ac6373" + token_addresses: + - "0x3B78576F7D6837500bA3De27A60c7f594934027E" +- earner_address: "0x025246421e7247a729bbcff652c5cc1815ac6373" + token_addresses: + - "0x3B78576F7D6837500bA3De27A60c7f594934027E" + \ No newline at end of file From 36b5a641f74fd3d394464b54fba2c91ad6d13d5d Mon Sep 17 00:00:00 2001 From: gpabst Date: Thu, 12 Dec 2024 15:01:55 -0500 Subject: [PATCH 2/4] feat: set and get operator split command (#247) --- go.mod | 4 +- go.sum | 6 +- pkg/internal/common/flags/general.go | 7 + pkg/operator.go | 2 + pkg/operator/get_operator_split.go | 124 ++++++++++++++++ pkg/operator/set_operator_split.go | 202 +++++++++++++++++++++++++++ pkg/operator/split/flags.go | 20 +++ pkg/operator/split/types.go | 33 +++++ 8 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 pkg/operator/get_operator_split.go create mode 100644 pkg/operator/set_operator_split.go create mode 100644 pkg/operator/split/flags.go create mode 100644 pkg/operator/split/types.go diff --git a/go.mod b/go.mod index cbdc0230..1cf783d0 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241121204729-7d2cd162ffe8 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886 github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 @@ -19,6 +19,7 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 github.com/urfave/cli/v2 v2.27.2 github.com/wagslane/go-password-validator v0.3.0 + github.com/wealdtech/go-merkletree/v2 v2.5.2-0.20240302222400-69219c450662 github.com/wk8/go-ordered-map/v2 v2.1.8 go.uber.org/mock v0.4.0 gopkg.in/yaml.v2 v2.4.0 @@ -100,7 +101,6 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/wealdtech/go-merkletree/v2 v2.5.2-0.20240302222400-69219c450662 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect diff --git a/go.sum b/go.sum index f893523b..7d2d824e 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,8 @@ github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 h1:G5Q1SnLmFbEjhOkky3vIHk github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12/go.mod h1:OlJd1QjqEW53wfWG/lJyPCGvrXwWVEjPQsP4TV+gttQ= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e h1:DvW0/kWHV9mZsbH2KOjEHKTSIONNPUj6X05FJvUohy4= github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= -github.com/Layr-Labs/eigensdk-go v0.1.13-0.20241023200243-565bb4438918 h1:Itl141PoMFzq58ZTo4Nu/CyH+x8f4BH6OmBNhZ6Z2/I= -github.com/Layr-Labs/eigensdk-go v0.1.13-0.20241023200243-565bb4438918/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241121204729-7d2cd162ffe8 h1:6wuVq+Elto+yF7bQ3QYqD2psxGXR3wcJh2koNcUjIQM= -github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241121204729-7d2cd162ffe8/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886 h1:+7AijqdfRXdDc3zvj02Alqsk6Qd3owvlqPYQN1Hc1ME= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/internal/common/flags/general.go b/pkg/internal/common/flags/general.go index 2ae9378c..4077c1a5 100644 --- a/pkg/internal/common/flags/general.go +++ b/pkg/internal/common/flags/general.go @@ -91,6 +91,13 @@ var ( Value: 3600, } + OperatorAddressFlag = cli.StringFlag{ + Name: "operator-address", + Aliases: []string{"oa", "operator"}, + Usage: "Operator address", + EnvVars: []string{"OPERATOR_ADDRESS"}, + } + BatchClaimFile = cli.StringFlag{ Name: "batch-claim-file", Aliases: []string{"bcf"}, diff --git a/pkg/operator.go b/pkg/operator.go index f3ca2ce4..7458852f 100644 --- a/pkg/operator.go +++ b/pkg/operator.go @@ -18,6 +18,8 @@ func OperatorCmd(p utils.Prompter) *cli.Command { operator.UpdateCmd(p), operator.UpdateMetadataURICmd(p), operator.GetApprovalCmd(p), + operator.SetOperatorSplitCmd(p), + operator.GetOperatorSplitCmd(p), }, } diff --git a/pkg/operator/get_operator_split.go b/pkg/operator/get_operator_split.go new file mode 100644 index 00000000..b9571474 --- /dev/null +++ b/pkg/operator/get_operator_split.go @@ -0,0 +1,124 @@ +package operator + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/operator/split" + "github.com/Layr-Labs/eigenlayer-cli/pkg/rewards" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" +) + +func GetOperatorSplitCmd(p utils.Prompter) *cli.Command { + var operatorSplitCmd = &cli.Command{ + Name: "get-rewards-split", + Usage: "Get operator rewards split", + Action: func(cCtx *cli.Context) error { + return GetOperatorSplit(cCtx) + }, + After: telemetry.AfterRunAction(), + Flags: getGetOperatorSplitFlags(), + } + + return operatorSplitCmd +} + +func getGetOperatorSplitFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.ETHRpcUrlFlag, + &flags.OperatorAddressFlag, + &split.OperatorSplitFlag, + &rewards.RewardsCoordinatorAddressFlag, + &split.AVSAddressFlag, + } + + sort.Sort(cli.FlagsByName(baseFlags)) + return baseFlags +} + +func GetOperatorSplit(cCtx *cli.Context) error { + ctx := cCtx.Context + logger := common.GetLogger(cCtx) + + config, err := readAndValidateGetOperatorSplitConfig(cCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate operator split config", err) + } + + cCtx.App.Metadata["network"] = config.ChainID.String() + + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new eth client", err) + } + + elReader, err := elcontracts.NewReaderFromConfig( + elcontracts.Config{ + RewardsCoordinatorAddress: config.RewardsCoordinatorAddress, + }, + ethClient, + logger, + ) + + if err != nil { + return eigenSdkUtils.WrapError("failed to get EL writer", err) + } + + logger.Infof("Getting operator split...") + + split, err := elReader.GetOperatorAVSSplit(ctx, config.OperatorAddress, config.AVSAddress) + + if err != nil { + return eigenSdkUtils.WrapError("failed to get operator split", err) + } + + logger.Infof("Operator split is %d", split) + + return nil +} + +func readAndValidateGetOperatorSplitConfig( + cCtx *cli.Context, + logger logging.Logger, +) (*split.GetOperatorAVSSplitConfig, error) { + network := cCtx.String(flags.NetworkFlag.Name) + rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) + + rewardsCoordinatorAddress := cCtx.String(rewards.RewardsCoordinatorAddressFlag.Name) + + var err error + if common.IsEmptyString(rewardsCoordinatorAddress) { + rewardsCoordinatorAddress, err = common.GetRewardCoordinatorAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + logger.Debugf("Using Rewards Coordinator address: %s", rewardsCoordinatorAddress) + + operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + logger.Infof("Using operator address: %s", operatorAddress.String()) + + avsAddress := gethcommon.HexToAddress(cCtx.String(split.AVSAddressFlag.Name)) + logger.Infof("Using AVS address: %s", avsAddress.String()) + + chainID := utils.NetworkNameToChainId(network) + logger.Debugf("Using chain ID: %s", chainID.String()) + + return &split.GetOperatorAVSSplitConfig{ + Network: network, + RPCUrl: rpcUrl, + RewardsCoordinatorAddress: gethcommon.HexToAddress(rewardsCoordinatorAddress), + ChainID: chainID, + OperatorAddress: operatorAddress, + AVSAddress: avsAddress, + }, nil +} diff --git a/pkg/operator/set_operator_split.go b/pkg/operator/set_operator_split.go new file mode 100644 index 00000000..3c6c8460 --- /dev/null +++ b/pkg/operator/set_operator_split.go @@ -0,0 +1,202 @@ +package operator + +import ( + "fmt" + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/operator/split" + "github.com/Layr-Labs/eigenlayer-cli/pkg/rewards" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/urfave/cli/v2" +) + +func SetOperatorSplitCmd(p utils.Prompter) *cli.Command { + var operatorSplitCmd = &cli.Command{ + Name: "set-rewards-split", + Usage: "Set operator rewards split", + Action: func(cCtx *cli.Context) error { + return SetOperatorSplit(cCtx, p) + }, + After: telemetry.AfterRunAction(), + Flags: getSetOperatorSplitFlags(), + } + + return operatorSplitCmd +} + +func SetOperatorSplit(cCtx *cli.Context, p utils.Prompter) error { + ctx := cCtx.Context + logger := common.GetLogger(cCtx) + + config, err := readAndValidateSetOperatorSplitConfig(cCtx, logger) + if err != nil { + return eigenSdkUtils.WrapError("failed to read and validate operator split config", err) + } + + cCtx.App.Metadata["network"] = config.ChainID.String() + + ethClient, err := ethclient.Dial(config.RPCUrl) + if err != nil { + return eigenSdkUtils.WrapError("failed to create new eth client", err) + } + + if config.Broadcast { + + eLWriter, err := common.GetELWriter( + config.OperatorAddress, + config.SignerConfig, + ethClient, + elcontracts.Config{ + RewardsCoordinatorAddress: config.RewardsCoordinatorAddress, + }, + p, + config.ChainID, + logger, + ) + + if err != nil { + return eigenSdkUtils.WrapError("failed to get EL writer", err) + } + + logger.Infof("Broadcasting set operator transaction...") + + receipt, err := eLWriter.SetOperatorAVSSplit(ctx, config.OperatorAddress, config.AVSAddress, config.Split, true) + + if err != nil { + return eigenSdkUtils.WrapError("failed to process claim", err) + } + + logger.Infof("Set operator transaction submitted successfully") + common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID) + } else { + noSendTxOpts := common.GetNoSendTxOpts(config.OperatorAddress) + _, _, contractBindings, err := elcontracts.BuildClients(elcontracts.Config{ + RewardsCoordinatorAddress: config.RewardsCoordinatorAddress, + }, ethClient, nil, logger, nil) + if err != nil { + return err + } + + code, err := ethClient.CodeAt(ctx, config.OperatorAddress, nil) + if err != nil { + return eigenSdkUtils.WrapError("failed to get code at address", err) + } + if len(code) > 0 { + // Operator is a smart contract + noSendTxOpts.GasLimit = 150_000 + } + + unsignedTx, err := contractBindings.RewardsCoordinator.SetOperatorAVSSplit(noSendTxOpts, config.OperatorAddress, config.AVSAddress, config.Split) + + if err != nil { + return eigenSdkUtils.WrapError("failed to create unsigned tx", err) + } + if config.OutputType == string(common.OutputType_Calldata) { + calldataHex := gethcommon.Bytes2Hex(unsignedTx.Data()) + + if !common.IsEmptyString(config.OutputFile) { + err = common.WriteToFile([]byte(calldataHex), config.OutputFile) + if err != nil { + return err + } + logger.Infof("Call data written to file: %s", config.OutputFile) + } else { + fmt.Println(calldataHex) + } + } else { + logger.Infof("This transaction would set the operator split to %d", config.Split) + } + + if !config.IsSilent { + txFeeDetails := common.GetTxFeeDetails(unsignedTx) + fmt.Println() + txFeeDetails.Print() + + fmt.Println("To broadcast the operator set split, use the --broadcast flag") + } + } + return nil +} + +func getSetOperatorSplitFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.ETHRpcUrlFlag, + &flags.OperatorAddressFlag, + &split.OperatorSplitFlag, + &rewards.RewardsCoordinatorAddressFlag, + &split.AVSAddressFlag, + &flags.BroadcastFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &flags.SilentFlag, + } + + allFlags := append(baseFlags, flags.GetSignerFlags()...) + sort.Sort(cli.FlagsByName(allFlags)) + return allFlags +} + +func readAndValidateSetOperatorSplitConfig( + cCtx *cli.Context, + logger logging.Logger, +) (*split.SetOperatorAVSSplitConfig, error) { + network := cCtx.String(flags.NetworkFlag.Name) + rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) + opSplit := cCtx.Int(split.OperatorSplitFlag.Name) + broadcast := cCtx.Bool(flags.BroadcastFlag.Name) + outputType := cCtx.String(flags.OutputTypeFlag.Name) + outputFile := cCtx.String(flags.OutputFileFlag.Name) + isSilent := cCtx.Bool(flags.SilentFlag.Name) + + rewardsCoordinatorAddress := cCtx.String(rewards.RewardsCoordinatorAddressFlag.Name) + + var err error + if common.IsEmptyString(rewardsCoordinatorAddress) { + rewardsCoordinatorAddress, err = common.GetRewardCoordinatorAddress(utils.NetworkNameToChainId(network)) + if err != nil { + return nil, err + } + } + logger.Debugf("Using Rewards Coordinator address: %s", rewardsCoordinatorAddress) + + operatorAddress := gethcommon.HexToAddress(cCtx.String(flags.OperatorAddressFlag.Name)) + logger.Infof("Using operator address: %s", operatorAddress.String()) + + avsAddress := gethcommon.HexToAddress(cCtx.String(split.AVSAddressFlag.Name)) + logger.Infof("Using AVS address: %s", avsAddress.String()) + + chainID := utils.NetworkNameToChainId(network) + logger.Debugf("Using chain ID: %s", chainID.String()) + + // Get SignerConfig + signerConfig, err := common.GetSignerConfig(cCtx, logger) + if err != nil { + // We don't want to throw error since people can still use it to generate the claim + // without broadcasting it + logger.Debugf("Failed to get signer config: %s", err) + } + + return &split.SetOperatorAVSSplitConfig{ + Network: network, + RPCUrl: rpcUrl, + RewardsCoordinatorAddress: gethcommon.HexToAddress(rewardsCoordinatorAddress), + ChainID: chainID, + SignerConfig: signerConfig, + OperatorAddress: operatorAddress, + AVSAddress: avsAddress, + Split: uint16(opSplit), + Broadcast: broadcast, + OutputType: outputType, + OutputFile: outputFile, + IsSilent: isSilent, + }, nil +} diff --git a/pkg/operator/split/flags.go b/pkg/operator/split/flags.go new file mode 100644 index 00000000..c8ec6bcf --- /dev/null +++ b/pkg/operator/split/flags.go @@ -0,0 +1,20 @@ +package split + +import "github.com/urfave/cli/v2" + +var ( + OperatorSplitFlag = cli.IntFlag{ + Name: "operator-split", + Aliases: []string{"os"}, + Usage: "Split for the operator in bips (e.g. 1000 = 10%)", + Required: false, + EnvVars: []string{"OPERATOR_SPLIT"}, + } + + AVSAddressFlag = cli.StringFlag{ + Name: "avs-address", + Aliases: []string{"aa"}, + Usage: "AVS address to set operator split", + EnvVars: []string{"AVS_ADDRESS"}, + } +) diff --git a/pkg/operator/split/types.go b/pkg/operator/split/types.go new file mode 100644 index 00000000..fd98bc39 --- /dev/null +++ b/pkg/operator/split/types.go @@ -0,0 +1,33 @@ +package split + +import ( + "math/big" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/types" + gethcommon "github.com/ethereum/go-ethereum/common" +) + +type SetOperatorAVSSplitConfig struct { + Network string + RPCUrl string + RewardsCoordinatorAddress gethcommon.Address + ChainID *big.Int + SignerConfig *types.SignerConfig + Broadcast bool + OperatorAddress gethcommon.Address + AVSAddress gethcommon.Address + Split uint16 + OutputType string + OutputFile string + IsSilent bool +} + +type GetOperatorAVSSplitConfig struct { + Network string + RPCUrl string + RewardsCoordinatorAddress gethcommon.Address + ChainID *big.Int + + OperatorAddress gethcommon.Address + AVSAddress gethcommon.Address +} From b2ab2c4c04705e6335fc0654f16fcd91b2575dac Mon Sep 17 00:00:00 2001 From: gpabst Date: Fri, 13 Dec 2024 08:39:52 -0500 Subject: [PATCH 3/4] feat: get/set operator PI split commands (#256) --- go.mod | 2 +- go.sum | 2 ++ pkg/operator.go | 2 ++ pkg/operator/get_operator_pi_split.go | 39 ++++++++++++++++++++++++ pkg/operator/get_operator_split.go | 19 ++++++++---- pkg/operator/set_operator_pi_split.go | 44 +++++++++++++++++++++++++++ pkg/operator/set_operator_split.go | 27 ++++++++++++---- 7 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 pkg/operator/get_operator_pi_split.go create mode 100644 pkg/operator/set_operator_pi_split.go diff --git a/go.mod b/go.mod index 1cf783d0..d96aada1 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Layr-Labs/eigenlayer-contracts v0.3.2-mainnet-rewards github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.12 github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e - github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886 + github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe github.com/blang/semver/v4 v4.0.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum/go-ethereum v1.14.5 diff --git a/go.sum b/go.sum index 7d2d824e..3ec027e3 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248- github.com/Layr-Labs/eigenpod-proofs-generation v0.0.14-stable.0.20240730152248-5c11a259293e/go.mod h1:T7tYN8bTdca2pkMnz9G2+ZwXYWw5gWqQUIu4KLgC/vM= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886 h1:+7AijqdfRXdDc3zvj02Alqsk6Qd3owvlqPYQN1Hc1ME= github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241211225219-79336bf6e886/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe h1:FeXxapvtEbbTbEWsrcBTTzQ2u2quGJ9HNYQVSk5JZ8g= +github.com/Layr-Labs/eigensdk-go v0.1.14-0.20241212190947-9985122d81fe/go.mod h1:aYdNURUhaqeYOS+Cq12TfSdPbjFfiLaHkxPdR4Exq/s= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= diff --git a/pkg/operator.go b/pkg/operator.go index 7458852f..d34d1742 100644 --- a/pkg/operator.go +++ b/pkg/operator.go @@ -20,6 +20,8 @@ func OperatorCmd(p utils.Prompter) *cli.Command { operator.GetApprovalCmd(p), operator.SetOperatorSplitCmd(p), operator.GetOperatorSplitCmd(p), + operator.GetOperatorPISplitCmd(p), + operator.SetOperatorPISplitCmd(p), }, } diff --git a/pkg/operator/get_operator_pi_split.go b/pkg/operator/get_operator_pi_split.go new file mode 100644 index 00000000..3e6a4cce --- /dev/null +++ b/pkg/operator/get_operator_pi_split.go @@ -0,0 +1,39 @@ +package operator + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/operator/split" + "github.com/Layr-Labs/eigenlayer-cli/pkg/rewards" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/urfave/cli/v2" +) + +func GetOperatorPISplitCmd(p utils.Prompter) *cli.Command { + var operatorSplitCmd = &cli.Command{ + Name: "get-pi-split", + Usage: "Get programmatic incentives rewards split", + Action: func(cCtx *cli.Context) error { + return GetOperatorSplit(cCtx, true) + }, + After: telemetry.AfterRunAction(), + Flags: getGetOperatorPISplitFlags(), + } + + return operatorSplitCmd +} + +func getGetOperatorPISplitFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.ETHRpcUrlFlag, + &flags.OperatorAddressFlag, + &split.OperatorSplitFlag, + &rewards.RewardsCoordinatorAddressFlag, + } + + sort.Sort(cli.FlagsByName(baseFlags)) + return baseFlags +} diff --git a/pkg/operator/get_operator_split.go b/pkg/operator/get_operator_split.go index b9571474..b12d47b2 100644 --- a/pkg/operator/get_operator_split.go +++ b/pkg/operator/get_operator_split.go @@ -22,7 +22,7 @@ func GetOperatorSplitCmd(p utils.Prompter) *cli.Command { Name: "get-rewards-split", Usage: "Get operator rewards split", Action: func(cCtx *cli.Context) error { - return GetOperatorSplit(cCtx) + return GetOperatorSplit(cCtx, false) }, After: telemetry.AfterRunAction(), Flags: getGetOperatorSplitFlags(), @@ -45,11 +45,11 @@ func getGetOperatorSplitFlags() []cli.Flag { return baseFlags } -func GetOperatorSplit(cCtx *cli.Context) error { +func GetOperatorSplit(cCtx *cli.Context, isProgrammaticIncentive bool) error { ctx := cCtx.Context logger := common.GetLogger(cCtx) - config, err := readAndValidateGetOperatorSplitConfig(cCtx, logger) + config, err := readAndValidateGetOperatorSplitConfig(cCtx, logger, isProgrammaticIncentive) if err != nil { return eigenSdkUtils.WrapError("failed to read and validate operator split config", err) } @@ -75,8 +75,12 @@ func GetOperatorSplit(cCtx *cli.Context) error { logger.Infof("Getting operator split...") - split, err := elReader.GetOperatorAVSSplit(ctx, config.OperatorAddress, config.AVSAddress) - + var split uint16 + if isProgrammaticIncentive { + split, err = elReader.GetOperatorPISplit(ctx, config.OperatorAddress) + } else { + split, err = elReader.GetOperatorAVSSplit(ctx, config.OperatorAddress, config.AVSAddress) + } if err != nil { return eigenSdkUtils.WrapError("failed to get operator split", err) } @@ -89,6 +93,7 @@ func GetOperatorSplit(cCtx *cli.Context) error { func readAndValidateGetOperatorSplitConfig( cCtx *cli.Context, logger logging.Logger, + isProgrammaticIncentive bool, ) (*split.GetOperatorAVSSplitConfig, error) { network := cCtx.String(flags.NetworkFlag.Name) rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) @@ -108,7 +113,9 @@ func readAndValidateGetOperatorSplitConfig( logger.Infof("Using operator address: %s", operatorAddress.String()) avsAddress := gethcommon.HexToAddress(cCtx.String(split.AVSAddressFlag.Name)) - logger.Infof("Using AVS address: %s", avsAddress.String()) + if !isProgrammaticIncentive { + logger.Infof("Using AVS address: %s", avsAddress.String()) + } chainID := utils.NetworkNameToChainId(network) logger.Debugf("Using chain ID: %s", chainID.String()) diff --git a/pkg/operator/set_operator_pi_split.go b/pkg/operator/set_operator_pi_split.go new file mode 100644 index 00000000..63919e3a --- /dev/null +++ b/pkg/operator/set_operator_pi_split.go @@ -0,0 +1,44 @@ +package operator + +import ( + "sort" + + "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common/flags" + "github.com/Layr-Labs/eigenlayer-cli/pkg/operator/split" + "github.com/Layr-Labs/eigenlayer-cli/pkg/rewards" + "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" + "github.com/Layr-Labs/eigenlayer-cli/pkg/utils" + "github.com/urfave/cli/v2" +) + +func SetOperatorPISplitCmd(p utils.Prompter) *cli.Command { + var operatorSplitCmd = &cli.Command{ + Name: "set-pi-split", + Usage: "Set operator programmatic incentives split", + Action: func(cCtx *cli.Context) error { + return SetOperatorSplit(cCtx, p, true) + }, + After: telemetry.AfterRunAction(), + Flags: getSetOperatorPISplitFlags(), + } + + return operatorSplitCmd +} + +func getSetOperatorPISplitFlags() []cli.Flag { + baseFlags := []cli.Flag{ + &flags.NetworkFlag, + &flags.ETHRpcUrlFlag, + &flags.OperatorAddressFlag, + &split.OperatorSplitFlag, + &rewards.RewardsCoordinatorAddressFlag, + &flags.BroadcastFlag, + &flags.OutputTypeFlag, + &flags.OutputFileFlag, + &flags.SilentFlag, + } + + allFlags := append(baseFlags, flags.GetSignerFlags()...) + sort.Sort(cli.FlagsByName(allFlags)) + return allFlags +} diff --git a/pkg/operator/set_operator_split.go b/pkg/operator/set_operator_split.go index 3c6c8460..2d5e43c1 100644 --- a/pkg/operator/set_operator_split.go +++ b/pkg/operator/set_operator_split.go @@ -14,6 +14,7 @@ import ( "github.com/Layr-Labs/eigensdk-go/logging" eigenSdkUtils "github.com/Layr-Labs/eigensdk-go/utils" gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/urfave/cli/v2" ) @@ -23,7 +24,7 @@ func SetOperatorSplitCmd(p utils.Prompter) *cli.Command { Name: "set-rewards-split", Usage: "Set operator rewards split", Action: func(cCtx *cli.Context) error { - return SetOperatorSplit(cCtx, p) + return SetOperatorSplit(cCtx, p, false) }, After: telemetry.AfterRunAction(), Flags: getSetOperatorSplitFlags(), @@ -32,11 +33,11 @@ func SetOperatorSplitCmd(p utils.Prompter) *cli.Command { return operatorSplitCmd } -func SetOperatorSplit(cCtx *cli.Context, p utils.Prompter) error { +func SetOperatorSplit(cCtx *cli.Context, p utils.Prompter, isProgrammaticIncentive bool) error { ctx := cCtx.Context logger := common.GetLogger(cCtx) - config, err := readAndValidateSetOperatorSplitConfig(cCtx, logger) + config, err := readAndValidateSetOperatorSplitConfig(cCtx, logger, isProgrammaticIncentive) if err != nil { return eigenSdkUtils.WrapError("failed to read and validate operator split config", err) } @@ -68,8 +69,13 @@ func SetOperatorSplit(cCtx *cli.Context, p utils.Prompter) error { logger.Infof("Broadcasting set operator transaction...") - receipt, err := eLWriter.SetOperatorAVSSplit(ctx, config.OperatorAddress, config.AVSAddress, config.Split, true) + var receipt *types.Receipt + if isProgrammaticIncentive { + receipt, err = eLWriter.SetOperatorPISplit(ctx, config.OperatorAddress, config.Split, true) + } else { + receipt, err = eLWriter.SetOperatorAVSSplit(ctx, config.OperatorAddress, config.AVSAddress, config.Split, true) + } if err != nil { return eigenSdkUtils.WrapError("failed to process claim", err) } @@ -94,7 +100,12 @@ func SetOperatorSplit(cCtx *cli.Context, p utils.Prompter) error { noSendTxOpts.GasLimit = 150_000 } - unsignedTx, err := contractBindings.RewardsCoordinator.SetOperatorAVSSplit(noSendTxOpts, config.OperatorAddress, config.AVSAddress, config.Split) + var unsignedTx *types.Transaction + if isProgrammaticIncentive { + unsignedTx, err = contractBindings.RewardsCoordinator.SetOperatorPISplit(noSendTxOpts, config.OperatorAddress, config.Split) + } else { + unsignedTx, err = contractBindings.RewardsCoordinator.SetOperatorAVSSplit(noSendTxOpts, config.OperatorAddress, config.AVSAddress, config.Split) + } if err != nil { return eigenSdkUtils.WrapError("failed to create unsigned tx", err) @@ -148,6 +159,7 @@ func getSetOperatorSplitFlags() []cli.Flag { func readAndValidateSetOperatorSplitConfig( cCtx *cli.Context, logger logging.Logger, + isProgrammaticIncentive bool, ) (*split.SetOperatorAVSSplitConfig, error) { network := cCtx.String(flags.NetworkFlag.Name) rpcUrl := cCtx.String(flags.ETHRpcUrlFlag.Name) @@ -172,7 +184,10 @@ func readAndValidateSetOperatorSplitConfig( logger.Infof("Using operator address: %s", operatorAddress.String()) avsAddress := gethcommon.HexToAddress(cCtx.String(split.AVSAddressFlag.Name)) - logger.Infof("Using AVS address: %s", avsAddress.String()) + + if !isProgrammaticIncentive { + logger.Infof("Using AVS address: %s", avsAddress.String()) + } chainID := utils.NetworkNameToChainId(network) logger.Debugf("Using chain ID: %s", chainID.String()) From 9cc23216cce860722a7d9cd8fc6ccbdda0321176 Mon Sep 17 00:00:00 2001 From: gpabst Date: Fri, 13 Dec 2024 09:04:43 -0500 Subject: [PATCH 4/4] feat: gate new rewards v2 commands to holesky --- pkg/operator/get_operator_split.go | 5 +++++ pkg/operator/set_operator_split.go | 5 +++++ pkg/rewards/claim.go | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/pkg/operator/get_operator_split.go b/pkg/operator/get_operator_split.go index b12d47b2..9e94c2e4 100644 --- a/pkg/operator/get_operator_split.go +++ b/pkg/operator/get_operator_split.go @@ -1,6 +1,7 @@ package operator import ( + "errors" "sort" "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" @@ -50,6 +51,10 @@ func GetOperatorSplit(cCtx *cli.Context, isProgrammaticIncentive bool) error { logger := common.GetLogger(cCtx) config, err := readAndValidateGetOperatorSplitConfig(cCtx, logger, isProgrammaticIncentive) + if config.Network != "holesky" { + return errors.New("getting operator split only supported on holesky with this version of CLI") + } + if err != nil { return eigenSdkUtils.WrapError("failed to read and validate operator split config", err) } diff --git a/pkg/operator/set_operator_split.go b/pkg/operator/set_operator_split.go index 2d5e43c1..aee16add 100644 --- a/pkg/operator/set_operator_split.go +++ b/pkg/operator/set_operator_split.go @@ -1,6 +1,7 @@ package operator import ( + "errors" "fmt" "sort" @@ -42,6 +43,10 @@ func SetOperatorSplit(cCtx *cli.Context, p utils.Prompter, isProgrammaticIncenti return eigenSdkUtils.WrapError("failed to read and validate operator split config", err) } + if config.Network != "holesky" { + return errors.New("setting operator split only supported on holesky with this version of CLI") + } + cCtx.App.Metadata["network"] = config.ChainID.String() ethClient, err := ethclient.Dial(config.RPCUrl) diff --git a/pkg/rewards/claim.go b/pkg/rewards/claim.go index fb078fca..c50a5127 100644 --- a/pkg/rewards/claim.go +++ b/pkg/rewards/claim.go @@ -222,6 +222,10 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error { return eigenSdkUtils.WrapError("failed to read and validate claim config", err) } + if config.Network != "holesky" && config.BatchClaimFile != "" { + return errors.New("batch claim only supported on holesky with this version of CLI") + } + cCtx.App.Metadata["network"] = config.ChainID.String() ethClient, err := ethclient.Dial(config.RPCUrl)