Skip to content

Commit

Permalink
feat: Rewards v2 batch claiming support
Browse files Browse the repository at this point in the history
  • Loading branch information
gpabst committed Nov 13, 2024
1 parent 4ab21e5 commit d1b3079
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 13 deletions.
7 changes: 7 additions & 0 deletions pkg/internal/common/flags/general.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,11 @@ var (
EnvVars: []string{"EXPIRY"},
Value: 3600,
}

BatchClaimFile = cli.StringFlag{
Name: "batchClaimFile",
Aliases: []string{"bcf"},
Usage: "Input file for batch rewards claim",
EnvVars: []string{"BATCH_CLAIM_FILE"},
}
)
157 changes: 144 additions & 13 deletions pkg/rewards/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"math/big"
"net/http"
"os"
"sort"
"strings"
"time"
Expand All @@ -15,6 +16,8 @@ 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"

Expand Down Expand Up @@ -74,14 +77,15 @@ func getClaimFlags() []cli.Flag {
&ProofStoreBaseURLFlag,
&flags.VerboseFlag,
&flags.SilentFlag,
&flags.BatchClaimFile,
}

allFlags := append(baseFlags, flags.GetSignerFlags()...)
sort.Sort(cli.FlagsByName(allFlags))
return allFlags
}

func Claim(cCtx *cli.Context, p utils.Prompter) error {
func BatchClaim(cCtx *cli.Context, p utils.Prompter) error {
ctx := cCtx.Context
logger := common.GetLogger(cCtx)

Expand Down Expand Up @@ -113,29 +117,108 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error {
http.DefaultClient,
)

claimDate, rootIndex, err := getClaimDistributionRoot(ctx, config.ClaimTimestamp, elReader, logger)
yamlFile, err := os.ReadFile(config.BatchClaimFile)
if err != nil {
return eigenSdkUtils.WrapError("failed to get claim distribution root", err)
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

for _, claimConfig := range claimConfigs {
earnerAddr := gethcommon.HexToAddress(claimConfig.EarnerAddress)

var tokenAddrs []gethcommon.Address
for _, addr := range claimConfig.TokenAddresses {
tokenAddrs = append(tokenAddrs, gethcommon.HexToAddress(addr))
}

elClaim, _, _, err := claimHelper(config.ClaimTimestamp, ctx, elReader, logger, earnerAddr, df, tokenAddrs)

elClaims = append(elClaims, *elClaim)

if err != nil {
logger.Errorf("Failed to process claim for earner %s: %v", earnerAddr.String(), err)
continue
}
}

if config.Broadcast {
eLWriter, err := common.GetELWriter(
config.ClaimerAddress,
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 batch claim transaction...")
receipt, err := eLWriter.ProcessClaims(ctx, elClaims, config.RecipientAddress, true)

Check failure on line 173 in pkg/rewards/claim.go

View workflow job for this annotation

GitHub Actions / Build

eLWriter.ProcessClaims undefined (type *elcontracts.ChainWriter has no field or method ProcessClaims)

Check failure on line 173 in pkg/rewards/claim.go

View workflow job for this annotation

GitHub Actions / Unit Test

eLWriter.ProcessClaims undefined (type *elcontracts.ChainWriter has no field or method ProcessClaims)

Check failure on line 173 in pkg/rewards/claim.go

View workflow job for this annotation

GitHub Actions / Lint

eLWriter.ProcessClaims undefined (type *elcontracts.ChainWriter has no field or method ProcessClaims)) (typecheck)

Check failure on line 173 in pkg/rewards/claim.go

View workflow job for this annotation

GitHub Actions / Lint

eLWriter.ProcessClaims undefined (type *elcontracts.ChainWriter has no field or method ProcessClaims) (typecheck)
if err != nil {
return eigenSdkUtils.WrapError("failed to process claim", err)
}

logger.Infof("Claim transaction submitted successfully")
common.PrintTransactionInfo(receipt.TxHash.String(), config.ChainID)
} else {
return eigenSdkUtils.WrapError("Batch claim currently only supported with broadcast flag", err)
}
return nil
}

func claimHelper(
claimTimestamp string,
ctx context.Context,
elReader *elcontracts.ChainReader,
logger logging.Logger,
earnerAddress gethcommon.Address,
df *httpProofDataFetcher.HttpProofDataFetcher,
tokenAddresses []gethcommon.Address,
) (*rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, *contractrewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim, *merkletree.MerkleTree, error) {
claimDate, rootIndex, err := getClaimDistributionRoot(ctx, claimTimestamp, elReader, logger)
if err != nil {
return nil, nil, nil, eigenSdkUtils.WrapError("failed to get claim distribution root", err)
}

proofData, err := df.FetchClaimAmountsForDate(ctx, claimDate)
if err != nil {
return eigenSdkUtils.WrapError("failed to fetch claim amounts for date", err)
return nil, nil, nil, eigenSdkUtils.WrapError("failed to fetch claim amounts for date", err)
}

claimableTokens, present := proofData.Distribution.GetTokensForEarner(config.EarnerAddress)
claimableTokens, present := proofData.Distribution.GetTokensForEarner(earnerAddress)
if !present {
return errors.New("no tokens claimable by earner")
return nil, nil, nil, errors.New("no tokens claimable by earner")
}

cg := claimgen.NewClaimgen(proofData.Distribution)
accounts, claim, err := cg.GenerateClaimProofForEarner(
config.EarnerAddress,
getTokensToClaim(claimableTokens, config.TokenAddresses),
earnerAddress,
getTokensToClaim(claimableTokens, tokenAddresses),
rootIndex,
)
if err != nil {
return eigenSdkUtils.WrapError("failed to generate claim proof for earner", err)
return nil, nil, nil, eigenSdkUtils.WrapError("failed to generate claim proof for earner", err)
}

if err != nil {
return nil, nil, nil, eigenSdkUtils.WrapError("failed to generate claim proof for earner", err)
}

elClaim := rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{
Expand All @@ -154,13 +237,59 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error {
logger.Info("Validating claim proof...")
ok, err := elReader.CheckClaim(ctx, elClaim)
if err != nil {
return err
return nil, nil, nil, err
}
if !ok {
return errors.New("failed to validate claim")
return nil, nil, nil, errors.New("failed to validate claim")
}
logger.Info("Claim proof validated successfully")

return &elClaim, claim, accounts, nil
}

func Claim(cCtx *cli.Context, p utils.Prompter) error {
ctx := cCtx.Context
logger := common.GetLogger(cCtx)

config, err := readAndValidateClaimConfig(cCtx, logger)
if err != nil {
return eigenSdkUtils.WrapError("failed to read and validate claim config", err)
}

if config.BatchClaimFile != "" {
return BatchClaim(cCtx, p)
}

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 create new reader from config", err)
}

df := httpProofDataFetcher.NewHttpProofDataFetcher(
config.ProofStoreBaseURL,
config.Environment,
config.Network,
http.DefaultClient,
)

elClaim, claim, accounts, err := claimHelper(config.ClaimTimestamp, ctx, elReader, logger, config.EarnerAddress, df, config.TokenAddresses)

if err != nil {
return err
}

if config.Broadcast {
eLWriter, err := common.GetELWriter(
config.ClaimerAddress,
Expand All @@ -179,7 +308,7 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error {
}

logger.Infof("Broadcasting claim transaction...")
receipt, err := eLWriter.ProcessClaim(ctx, elClaim, config.RecipientAddress, true)
receipt, err := eLWriter.ProcessClaim(ctx, *elClaim, config.RecipientAddress, true)
if err != nil {
return eigenSdkUtils.WrapError("failed to process claim", err)
}
Expand Down Expand Up @@ -208,7 +337,7 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error {
noSendTxOpts.GasLimit = 150_000
}

unsignedTx, err := contractBindings.RewardsCoordinator.ProcessClaim(noSendTxOpts, elClaim, config.RecipientAddress)
unsignedTx, err := contractBindings.RewardsCoordinator.ProcessClaim(noSendTxOpts, *elClaim, config.RecipientAddress)
if err != nil {
return eigenSdkUtils.WrapError("failed to create unsigned tx", err)
}
Expand Down Expand Up @@ -373,6 +502,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) {
Expand Down Expand Up @@ -457,6 +587,7 @@ func readAndValidateClaimConfig(cCtx *cli.Context, logger logging.Logger) (*Clai
ClaimTimestamp: claimTimestamp,
ClaimerAddress: claimerAddress,
IsSilent: isSilent,
BatchClaimFile: batchClaimFile,
}, nil
}

Expand Down
1 change: 1 addition & 0 deletions pkg/rewards/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type ClaimConfig struct {
Environment string
SignerConfig *types.SignerConfig
IsSilent bool
BatchClaimFile string
}

type SetClaimerConfig struct {
Expand Down
6 changes: 6 additions & 0 deletions samples/batch-claims.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- earner_address: "0x025246421e7247a729bbcff652c5cc1815ac6373"
token_addresses:
- "0x3B78576F7D6837500bA3De27A60c7f594934027E"
- earner_address: "0x025246421e7247a729bbcff652c5cc1815ac6373"
token_addresses:
- "0x3B78576F7D6837500bA3De27A60c7f594934027E"

0 comments on commit d1b3079

Please sign in to comment.