Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Rewards v2 batch claiming support #241

Merged
merged 25 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9d65637
feat: Rewards v2 batch claiming support
gpabst Nov 13, 2024
dbe1299
fix: update parameter name to batch-claim-file
gpabst Nov 15, 2024
abd0903
fix: remove duplicated code now that its not a separate command
gpabst Nov 15, 2024
d31dcfb
fix: remove dupe error check
gpabst Nov 15, 2024
f412523
fix: context should always be first argument
gpabst Nov 15, 2024
4f87cf8
fix: more descriptive name for claim helper
gpabst Nov 15, 2024
fe217d6
fix: include earner address in claim proof logs
gpabst Nov 15, 2024
ed9e526
chore: use updated eigensdk
gpabst Nov 25, 2024
74bcf53
chore: go fmt
gpabst Nov 25, 2024
ab89bb0
feat: claim all claimable tokens if token addresses is empty
gpabst Nov 25, 2024
c244b30
refactor: create broadcastClaims
gpabst Nov 27, 2024
5bda722
refactor: calculate rootIndex and proofData once
gpabst Nov 27, 2024
5d524f0
fix: move append after error check
gpabst Nov 27, 2024
acc0c8a
fix: make this log a warning
gpabst Nov 27, 2024
fce57b5
fix: pass in logger and context
gpabst Nov 27, 2024
bcbbc9e
fix: add newline
gpabst Nov 27, 2024
c5ec192
feat: initial support for outputing batch claim data
gpabst Nov 27, 2024
53ed596
fix: len > 1 not 0
gpabst Dec 3, 2024
2c2a3bf
chore: reduce dereferences
gpabst Dec 3, 2024
50fa8c3
chore: make BatchClaim private
gpabst Dec 3, 2024
2bb9f59
refactor: remove unused params
gpabst Dec 3, 2024
4b39263
fix: newline
gpabst Dec 3, 2024
39e47b8
fix: remove error log
gpabst Dec 3, 2024
4d4c8fe
fix: len > 1
gpabst Dec 3, 2024
8d85fed
fix: handle case with 0 claims
gpabst Dec 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
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: "batch-claim-file",
Aliases: []string{"bcf"},
Usage: "Input file for batch rewards claim",
EnvVars: []string{"BATCH_CLAIM_FILE"},
}
)
201 changes: 165 additions & 36 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"
gpabst marked this conversation as resolved.
Show resolved Hide resolved

contractrewardscoordinator "github.com/Layr-Labs/eigenlayer-contracts/pkg/bindings/IRewardsCoordinator"

Expand Down Expand Up @@ -75,75 +78,145 @@ 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(
gpabst marked this conversation as resolved.
Show resolved Hide resolved
cCtx *cli.Context,
gpabst marked this conversation as resolved.
Show resolved Hide resolved
ethClient *ethclient.Client,
elReader *elcontracts.ChainReader,
df *httpProofDataFetcher.HttpProofDataFetcher,
config *ClaimConfig,
p utils.Prompter,
) error {
ctx := cCtx.Context
logger := common.GetLogger(cCtx)
gpabst marked this conversation as resolved.
Show resolved Hide resolved

config, err := readAndValidateClaimConfig(cCtx, logger)
yamlFile, err := os.ReadFile(config.BatchClaimFile)
if err != nil {
return eigenSdkUtils.WrapError("failed to read and validate claim config", err)
return eigenSdkUtils.WrapError("failed to read YAML config file", 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)
var claimConfigs []struct {
EarnerAddress string `yaml:"earner_address"`
TokenAddresses []string `yaml:"token_addresses"`
}

elReader, err := elcontracts.NewReaderFromConfig(
elcontracts.Config{
RewardsCoordinatorAddress: config.RewardsCoordinatorAddress,
},
ethClient, logger,
)
err = yaml.Unmarshal(yamlFile, &claimConfigs)
if err != nil {
return eigenSdkUtils.WrapError("failed to create new reader from config", err)
return eigenSdkUtils.WrapError("failed to parse YAML config", err)
}

df := httpProofDataFetcher.NewHttpProofDataFetcher(
config.ProofStoreBaseURL,
config.Environment,
config.Network,
http.DefaultClient,
)
var elClaims []rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim

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 {
tokenAddrs = nil
} else {
for _, addr := range claimConfig.TokenAddresses {
tokenAddrs = append(tokenAddrs, gethcommon.HexToAddress(addr))
}
}
gpabst marked this conversation as resolved.
Show resolved Hide resolved

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

elClaims = append(elClaims, *elClaim)

if err != nil {
gpabst marked this conversation as resolved.
Show resolved Hide resolved
logger.Errorf("Failed to process claim for earner %s: %v", earnerAddr.String(), err)
gpabst marked this conversation as resolved.
Show resolved Hide resolved
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)
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)
gpabst marked this conversation as resolved.
Show resolved Hide resolved
gpabst marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
}

claimDate, rootIndex, err := getClaimDistributionRoot(ctx, config.ClaimTimestamp, elReader, logger)
func generateClaimPayload(
ctx context.Context,
claimTimestamp string,
elReader *elcontracts.ChainReader,
logger logging.Logger,
gpabst marked this conversation as resolved.
Show resolved Hide resolved
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 eigenSdkUtils.WrapError("failed to get claim distribution root", err)
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)
}
gpabst marked this conversation as resolved.
Show resolved Hide resolved

claimableTokensOrderMap, present := proofData.Distribution.GetTokensForEarner(config.EarnerAddress)
claimableTokensOrderMap, 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")
}

claimableTokensMap := getTokensToClaim(claimableTokensOrderMap, config.TokenAddresses)
claimableTokensMap := getTokensToClaim(claimableTokensOrderMap, tokenAddresses)

claimableTokens, err := filterClaimableTokens(ctx, elReader, config.EarnerAddress, claimableTokensMap)
claimableTokens, err := filterClaimableTokens(ctx, elReader, earnerAddress, claimableTokensMap)
if err != nil {
return eigenSdkUtils.WrapError("failed to get claimable tokens", err)
return nil, nil, nil, eigenSdkUtils.WrapError("failed to get claimable tokens", err)
}

cg := claimgen.NewClaimgen(proofData.Distribution)
accounts, claim, err := cg.GenerateClaimProofForEarner(
config.EarnerAddress,
earnerAddress,
claimableTokens,
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)
}

elClaim := rewardscoordinator.IRewardsCoordinatorRewardsMerkleClaim{
Expand All @@ -159,15 +232,69 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error {
TokenLeaves: convertClaimTokenLeaves(claim.TokenLeaves),
}

logger.Info("Validating claim proof...")
logger.Infof("Validating claim proof for earner %s...", earnerAddress)
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.Infof("Claim proof for earner %s validated successfully", earnerAddress)

return &elClaim, claim, accounts, nil
}

func Claim(cCtx *cli.Context, p utils.Prompter) error {
gpabst marked this conversation as resolved.
Show resolved Hide resolved
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)
}

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,
gpabst marked this conversation as resolved.
Show resolved Hide resolved
)
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,
)

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

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

if err != nil {
return err
}
logger.Info("Claim proof validated successfully")

if config.Broadcast {
eLWriter, err := common.GetELWriter(
Expand All @@ -187,7 +314,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 @@ -216,7 +343,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)
gpabst marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return eigenSdkUtils.WrapError("failed to create unsigned tx", err)
}
Expand Down Expand Up @@ -406,6 +533,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 @@ -490,6 +618,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"
Loading