Skip to content

Commit 5f6537f

Browse files
fix: already claimed tokens should not be in claim proof (#240)
1 parent 680b434 commit 5f6537f

File tree

3 files changed

+167
-31
lines changed

3 files changed

+167
-31
lines changed

pkg/rewards/claim.go

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type elChainReader interface {
4141
ctx context.Context,
4242
) (rewardscoordinator.IRewardsCoordinatorDistributionRoot, error)
4343
CurrRewardsCalculationEndTimestamp(ctx context.Context) (uint32, error)
44+
GetCumulativeClaimed(ctx context.Context, earnerAddress, tokenAddress gethcommon.Address) (*big.Int, error)
4445
}
4546

4647
func ClaimCmd(p utils.Prompter) *cli.Command {
@@ -123,15 +124,22 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error {
123124
return eigenSdkUtils.WrapError("failed to fetch claim amounts for date", err)
124125
}
125126

126-
claimableTokens, present := proofData.Distribution.GetTokensForEarner(config.EarnerAddress)
127+
claimableTokensOrderMap, present := proofData.Distribution.GetTokensForEarner(config.EarnerAddress)
127128
if !present {
128129
return errors.New("no tokens claimable by earner")
129130
}
130131

132+
claimableTokensMap := getTokensToClaim(claimableTokensOrderMap, config.TokenAddresses)
133+
134+
claimableTokens, err := filterClaimableTokens(ctx, elReader, config.EarnerAddress, claimableTokensMap)
135+
if err != nil {
136+
return eigenSdkUtils.WrapError("failed to get claimable tokens", err)
137+
}
138+
131139
cg := claimgen.NewClaimgen(proofData.Distribution)
132140
accounts, claim, err := cg.GenerateClaimProofForEarner(
133141
config.EarnerAddress,
134-
getTokensToClaim(claimableTokens, config.TokenAddresses),
142+
claimableTokens,
135143
rootIndex,
136144
)
137145
if err != nil {
@@ -270,6 +278,30 @@ func Claim(cCtx *cli.Context, p utils.Prompter) error {
270278
return nil
271279
}
272280

281+
// filterClaimableTokens to filter out tokens that have been fully claimed
282+
func filterClaimableTokens(
283+
ctx context.Context,
284+
elReader elChainReader,
285+
earnerAddress gethcommon.Address,
286+
claimableTokensMap map[gethcommon.Address]*big.Int,
287+
) ([]gethcommon.Address, error) {
288+
claimableTokens := make([]gethcommon.Address, 0)
289+
for token, claimedAmount := range claimableTokensMap {
290+
amount, err := getCummulativeClaimedRewards(ctx, elReader, earnerAddress, token)
291+
if err != nil {
292+
return nil, err
293+
}
294+
// If the token has been claimed fully, we don't need to include it in the claim
295+
// This is because contracts reject claims for tokens that have been fully claimed
296+
// https://github.com/Layr-Labs/eigenlayer-contracts/blob/ac57bc1b28c83d9d7143c0da19167c148c3596a3/src/contracts/core/RewardsCoordinator.sol#L575-L578
297+
if claimedAmount.Cmp(amount) == 0 {
298+
continue
299+
}
300+
claimableTokens = append(claimableTokens, token)
301+
}
302+
return claimableTokens, nil
303+
}
304+
273305
func getClaimDistributionRoot(
274306
ctx context.Context,
275307
claimTimestamp string,
@@ -312,39 +344,40 @@ func getClaimDistributionRoot(
312344
func getTokensToClaim(
313345
claimableTokens *orderedmap.OrderedMap[gethcommon.Address, *distribution.BigInt],
314346
tokenAddresses []gethcommon.Address,
315-
) []gethcommon.Address {
347+
) map[gethcommon.Address]*big.Int {
348+
var tokenMap map[gethcommon.Address]*big.Int
316349
if len(tokenAddresses) == 0 {
317-
tokenAddresses = getAllClaimableTokenAddresses(claimableTokens)
350+
tokenMap = getAllClaimableTokenAddresses(claimableTokens)
318351
} else {
319-
tokenAddresses = filterClaimableTokenAddresses(claimableTokens, tokenAddresses)
352+
tokenMap = filterClaimableTokenAddresses(claimableTokens, tokenAddresses)
320353
}
321354

322-
return tokenAddresses
355+
return tokenMap
323356
}
324357

325358
func getAllClaimableTokenAddresses(
326359
addressesMap *orderedmap.OrderedMap[gethcommon.Address, *distribution.BigInt],
327-
) []gethcommon.Address {
328-
var addresses []gethcommon.Address
360+
) map[gethcommon.Address]*big.Int {
361+
tokens := make(map[gethcommon.Address]*big.Int)
329362
for pair := addressesMap.Oldest(); pair != nil; pair = pair.Next() {
330-
addresses = append(addresses, pair.Key)
363+
tokens[pair.Key] = pair.Value.Int
331364
}
332365

333-
return addresses
366+
return tokens
334367
}
335368

336369
func filterClaimableTokenAddresses(
337370
addressesMap *orderedmap.OrderedMap[gethcommon.Address, *distribution.BigInt],
338371
providedAddresses []gethcommon.Address,
339-
) []gethcommon.Address {
340-
var addresses []gethcommon.Address
372+
) map[gethcommon.Address]*big.Int {
373+
tokens := make(map[gethcommon.Address]*big.Int)
341374
for _, address := range providedAddresses {
342-
if _, ok := addressesMap.Get(address); ok {
343-
addresses = append(addresses, address)
375+
if val, ok := addressesMap.Get(address); ok {
376+
tokens[address] = val.Int
344377
}
345378
}
346379

347-
return addresses
380+
return tokens
348381
}
349382

350383
func convertClaimTokenLeaves(

pkg/rewards/claim_test.go

Lines changed: 102 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ import (
2727
)
2828

2929
type fakeELReader struct {
30-
roots []rewardscoordinator.IRewardsCoordinatorDistributionRoot
30+
roots []rewardscoordinator.IRewardsCoordinatorDistributionRoot
31+
earnerTokenClaimedMap map[common.Address]map[common.Address]*big.Int
3132
}
3233

33-
func newFakeELReader(now time.Time) *fakeELReader {
34+
func newFakeELReader(
35+
now time.Time,
36+
earnerTokenClaimedMap map[common.Address]map[common.Address]*big.Int,
37+
) *fakeELReader {
3438
roots := make([]rewardscoordinator.IRewardsCoordinatorDistributionRoot, 0)
3539
rootOne := rewardscoordinator.IRewardsCoordinatorDistributionRoot{
3640
Root: [32]byte{0x01},
@@ -60,7 +64,8 @@ func newFakeELReader(now time.Time) *fakeELReader {
6064
return roots[i].ActivatedAt < roots[j].ActivatedAt
6165
})
6266
return &fakeELReader{
63-
roots: roots,
67+
roots: roots,
68+
earnerTokenClaimedMap: earnerTokenClaimedMap,
6469
}
6570
}
6671

@@ -91,6 +96,21 @@ func (f *fakeELReader) GetCurrentClaimableDistributionRoot(
9196
return rewardscoordinator.IRewardsCoordinatorDistributionRoot{}, errors.New("no active distribution root found")
9297
}
9398

99+
func (f *fakeELReader) GetCumulativeClaimed(
100+
ctx context.Context,
101+
earnerAddress,
102+
tokenAddress common.Address,
103+
) (*big.Int, error) {
104+
if f.earnerTokenClaimedMap == nil {
105+
return big.NewInt(0), nil
106+
}
107+
claimed, ok := f.earnerTokenClaimedMap[earnerAddress][tokenAddress]
108+
if !ok {
109+
return big.NewInt(0), nil
110+
}
111+
return claimed, nil
112+
}
113+
94114
func (f *fakeELReader) CurrRewardsCalculationEndTimestamp(ctx context.Context) (uint32, error) {
95115
rootLen, err := f.GetDistributionRootsLength(ctx)
96116
if err != nil {
@@ -246,7 +266,7 @@ func TestGetClaimDistributionRoot(t *testing.T) {
246266
},
247267
}
248268

249-
reader := newFakeELReader(now)
269+
reader := newFakeELReader(now, nil)
250270
logger := logging.NewJsonSLogger(os.Stdout, &logging.SLoggerOptions{})
251271

252272
for _, tt := range tests {
@@ -280,13 +300,18 @@ func TestGetTokensToClaim(t *testing.T) {
280300

281301
// Case 1: No token addresses provided, should return all addresses in claimableTokens
282302
result := getTokensToClaim(claimableTokens, []common.Address{})
283-
expected := []common.Address{addr1, addr2}
284-
assert.ElementsMatch(t, result, expected)
303+
expected := map[common.Address]*big.Int{
304+
addr1: big.NewInt(100),
305+
addr2: big.NewInt(200),
306+
}
307+
assert.Equal(t, result, expected)
285308

286309
// Case 2: Provided token addresses, should return only those present in claimableTokens
287310
result = getTokensToClaim(claimableTokens, []common.Address{addr2, addr3})
288-
expected = []common.Address{addr2}
289-
assert.ElementsMatch(t, result, expected)
311+
expected = map[common.Address]*big.Int{
312+
addr2: big.NewInt(200),
313+
}
314+
assert.Equal(t, result, expected)
290315
}
291316

292317
func TestGetTokenAddresses(t *testing.T) {
@@ -300,8 +325,11 @@ func TestGetTokenAddresses(t *testing.T) {
300325

301326
// Test that the function returns all addresses in the map
302327
result := getAllClaimableTokenAddresses(addressesMap)
303-
expected := []common.Address{addr1, addr2}
304-
assert.ElementsMatch(t, result, expected)
328+
expected := map[common.Address]*big.Int{
329+
addr1: big.NewInt(100),
330+
addr2: big.NewInt(200),
331+
}
332+
assert.Equal(t, result, expected)
305333
}
306334

307335
func TestFilterClaimableTokenAddresses(t *testing.T) {
@@ -321,8 +349,70 @@ func TestFilterClaimableTokenAddresses(t *testing.T) {
321349
}
322350

323351
result := filterClaimableTokenAddresses(addressesMap, providedAddresses)
324-
expected := []common.Address{addr1}
325-
assert.ElementsMatch(t, result, expected)
352+
expected := map[common.Address]*big.Int{
353+
addr1: big.NewInt(100),
354+
}
355+
assert.Equal(t, result, expected)
356+
}
357+
358+
func TestFilterClaimableTokens(t *testing.T) {
359+
// Set up a mock claimableTokens map
360+
earnerAddress := common.HexToAddress(testutils.GenerateRandomEthereumAddressString())
361+
tokenAddress1 := common.HexToAddress(testutils.GenerateRandomEthereumAddressString())
362+
tokenAddress2 := common.HexToAddress(testutils.GenerateRandomEthereumAddressString())
363+
amountClaimed1 := big.NewInt(100)
364+
amountClaimed2 := big.NewInt(200)
365+
elReaderClaimedMap := map[common.Address]map[common.Address]*big.Int{
366+
earnerAddress: {
367+
tokenAddress1: amountClaimed1,
368+
tokenAddress2: amountClaimed2,
369+
},
370+
}
371+
now := time.Now()
372+
reader := newFakeELReader(now, elReaderClaimedMap)
373+
tests := []struct {
374+
name string
375+
earnerAddress common.Address
376+
claimableTokensMap map[common.Address]*big.Int
377+
expectedClaimableTokens []common.Address
378+
}{
379+
{
380+
name: "all tokens are claimable and non zero",
381+
earnerAddress: earnerAddress,
382+
claimableTokensMap: map[common.Address]*big.Int{
383+
tokenAddress1: big.NewInt(2345),
384+
tokenAddress2: big.NewInt(3345),
385+
},
386+
expectedClaimableTokens: []common.Address{
387+
tokenAddress1,
388+
tokenAddress2,
389+
},
390+
},
391+
{
392+
name: "one token is already claimed",
393+
earnerAddress: earnerAddress,
394+
claimableTokensMap: map[common.Address]*big.Int{
395+
tokenAddress1: amountClaimed1,
396+
tokenAddress2: big.NewInt(1234),
397+
},
398+
expectedClaimableTokens: []common.Address{
399+
tokenAddress2,
400+
},
401+
},
402+
}
403+
404+
for _, tt := range tests {
405+
t.Run(tt.name, func(t *testing.T) {
406+
result, err := filterClaimableTokens(
407+
context.Background(),
408+
reader,
409+
tt.earnerAddress,
410+
tt.claimableTokensMap,
411+
)
412+
assert.NoError(t, err)
413+
assert.ElementsMatch(t, tt.expectedClaimableTokens, result)
414+
})
415+
}
326416
}
327417

328418
func newBigInt(value int64) *distribution.BigInt {

pkg/rewards/show.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,18 +169,31 @@ func getClaimedRewards(
169169
) (map[gethcommon.Address]*big.Int, error) {
170170
claimedRewards := make(map[gethcommon.Address]*big.Int)
171171
for address := range allRewards {
172-
claimed, err := elReader.GetCumulativeClaimed(ctx, earnerAddress, address)
172+
claimed, err := getCummulativeClaimedRewards(ctx, elReader, earnerAddress, address)
173173
if err != nil {
174174
return nil, err
175175
}
176-
if claimed == nil {
177-
claimed = big.NewInt(0)
178-
}
179176
claimedRewards[address] = claimed
180177
}
181178
return claimedRewards, nil
182179
}
183180

181+
func getCummulativeClaimedRewards(
182+
ctx context.Context,
183+
elReader ELReader,
184+
earnerAddress gethcommon.Address,
185+
tokenAddress gethcommon.Address,
186+
) (*big.Int, error) {
187+
claimed, err := elReader.GetCumulativeClaimed(ctx, earnerAddress, tokenAddress)
188+
if err != nil {
189+
return nil, err
190+
}
191+
if claimed == nil {
192+
claimed = big.NewInt(0)
193+
}
194+
return claimed, nil
195+
}
196+
184197
func calculateUnclaimedRewards(
185198
allRewards,
186199
claimedRewards map[gethcommon.Address]*big.Int,

0 commit comments

Comments
 (0)