Skip to content

Commit

Permalink
SQS-325 | Include unclaimed staking rewards into Unclaimed rewards ca…
Browse files Browse the repository at this point in the history
…tegory (#434)

* SQS-325 | Include unclaimed staking rewards into Unclaimed rewards category

This PR includes staking rewards to Unclaimed rewards category for
portfolio assets endpoint.

Check list:

* [x] - Unclaimed rewards include staking rewards
* [x] - Unit tests

```sh
❯ make test-unit
...
ok      github.com/osmosis-labs/sqs/passthrough/usecase (cached)
...
```
* [x] - Integration tests continue to pass

```sh
[nix-shell:~/go/src/github.com/deividaspetraitis/sqs]$ SQS_ENVIRONMENTS=local pytest -s -n 4 tests/test_passthrough.py
Session is starting. Worker ID: master
....
========================================================== test session starts ==========================================================
platform linux -- Python 3.11.9, pytest-8.2.1, pluggy-1.5.0
rootdir: /home/deividas/go/src/github.com/deividaspetraitis/sqs
plugins: xdist-3.6.1
4 workers [1 item]
.
=========================================================== 1 passed in 0.90s ===========================================================
(venv)
```

* [x] - Manually tested unclaimed rewards, total assets get updated

```sh
curl -X 'GET' \
	'https://sqs.stage.osmosis.zone/passthrough/portfolio-assets/osmo1zlymlax05tg9km9jyw496jx60v86m4548xw2xu' \
	-H 'accept: application/json'
```

```sh
curl -X 'GET' \
	'http://localhost:9092/passthrough/portfolio-assets/osmo1zlymlax05tg9km9jyw496jx60v86m4548xw2xu' \
	-H 'accept: application/json'
```

* [x] - Docs updated

* Update domain/passthrough/passthrough_grpc_client.go

---------

Co-authored-by: Roman <[email protected]>
  • Loading branch information
deividaspetraitis and p0mvn authored Aug 7, 2024
1 parent 65db797 commit b469f69
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 9 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Redis
redis-cache

Expand Down Expand Up @@ -26,3 +25,7 @@ sqs.log
# Vim swap files
.*.swp
.*.swo

# Delve debug
__debug_bin*
debug.test*
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ build-reproducible-arm64: go.sum
load-test-ui:
$(DOCKER) compose -f locust/docker-compose.yml up --scale worker=4

debug:
dlv --build-flags="-ldflags='-X github.com/osmosis-labs/sqs/version=${VERSION}'" debug app/*.go -- --config ./config.json

profile:
go tool pprof -http=:8080 http://localhost:9092/debug/pprof/profile?seconds=60

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/passthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Unstaking value from delegator undelegating chain query.

#### Unclaimed Rewards

Unclaimed rewards from concentrated liquidity positions.
Unclaimed rewards from concentrated liquidity positions and unclaimed rewards from staking rewards.
10 changes: 10 additions & 0 deletions domain/mocks/passthrough_grpc_client_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type PassthroughGRPCClientMock struct {
MockDelegatorDelegationsCb func(ctx context.Context, address string) (sdk.Coins, error)
MockDelegatorUnbondingDelegationsCb func(ctx context.Context, address string) (sdk.Coins, error)
MockUserPositionsBalancesCb func(ctx context.Context, address string) (sdk.Coins, sdk.Coins, error)
MockDelegationRewardsCb func(ctx context.Context, address string) (sdk.Coins, error)
}

// AccountLockedCoins implements passthroughdomain.PassthroughGRPCClient.
Expand Down Expand Up @@ -71,4 +72,13 @@ func (p *PassthroughGRPCClientMock) AccountUnlockingCoins(ctx context.Context, a
return nil, errors.New("MockAccountLockedCoinsCb is not implemented")
}

// DelegationRewards implements passthroughdomain.PassthroughGRPCClient.
func (p *PassthroughGRPCClientMock) DelegationRewards(ctx context.Context, address string) (sdk.Coins, error) {
if p.MockDelegationRewardsCb != nil {
return p.MockDelegationRewardsCb(ctx, address)
}

return nil, errors.New("MockDelegationRewardsCb is not implemented")
}

var _ passthroughdomain.PassthroughGRPCClient = &PassthroughGRPCClientMock{}
23 changes: 23 additions & 0 deletions domain/passthrough/passthrough_grpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
query "github.com/cosmos/cosmos-sdk/types/query"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
distribution "github.com/cosmos/cosmos-sdk/x/distribution/types"
staking "github.com/cosmos/cosmos-sdk/x/staking/types"
concentratedLiquidity "github.com/osmosis-labs/osmosis/v25/x/concentrated-liquidity/client/queryproto"
lockup "github.com/osmosis-labs/osmosis/v25/x/lockup/types"
Expand Down Expand Up @@ -34,6 +35,9 @@ type PassthroughGRPCClient interface {
// UserPositionsBalances returns the user concentrated positions balances of the user with the given address.
// The first return is the pooled balance. The second return is the reward balance.
UserPositionsBalances(ctx context.Context, address string) (sdk.Coins, sdk.Coins, error)

// DelegationTotalRewards returns the total unclaimed staking rewards accrued of the user with the given address.
DelegationRewards(ctx context.Context, address string) (sdk.Coins, error)
}

type PassthroughFetchFn func(context.Context, string) (sdk.Coins, error)
Expand All @@ -48,6 +52,7 @@ type passthroughGRPCClient struct {
stakingQueryClient staking.QueryClient
lockupQueryClient lockup.QueryClient
concentratedLiquidityQueryClient concentratedLiquidity.QueryClient
distributionClient distribution.QueryClient
}

const (
Expand All @@ -72,6 +77,7 @@ func NewPassthroughGRPCClient(grpcURI string) (PassthroughGRPCClient, error) {
stakingQueryClient: staking.NewQueryClient(grpcClient),
lockupQueryClient: lockup.NewQueryClient(grpcClient),
concentratedLiquidityQueryClient: concentratedLiquidity.NewQueryClient(grpcClient),
distributionClient: distribution.NewQueryClient(grpcClient),
}, nil
}

Expand Down Expand Up @@ -168,6 +174,23 @@ func (p *passthroughGRPCClient) UserPositionsBalances(ctx context.Context, addre
return pooledCoins, rewardCoins, nil
}

func (p *passthroughGRPCClient) DelegationRewards(ctx context.Context, address string) (sdk.Coins, error) {
response, err := p.distributionClient.DelegationTotalRewards(
ctx,
&distribution.QueryDelegationTotalRewardsRequest{DelegatorAddress: address},
)
if err != nil {
return nil, err
}

var rewardCoins = sdk.Coins{}
for _, v := range response.GetTotal() {
rewardCoins = append(rewardCoins, sdk.Coin{Denom: v.Denom, Amount: v.Amount.TruncateInt()})
}

return rewardCoins, nil
}

func paginateRequest(ctx context.Context, fetchCoinsFn func(ctx context.Context, pageRequest *query.PageRequest) (*query.PageResponse, sdk.Coins, error)) (sdk.Coins, error) {
var (
isFirstRequest = true
Expand Down
43 changes: 40 additions & 3 deletions passthrough/usecase/passthrough_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ const (
// 2. Concentrated positions
pooledBalancedNumJobs = 2

// Number of unclaimed rewards jobs to fetch concurrently.
// 1. Unclaimed rewards from concentrated positions
// 2. Unclaimed rewards from staking rewards
unclaimedRewardsNumJobs = 2

// locked + unlocking
numInLocksQueries = 2
)
Expand Down Expand Up @@ -131,7 +136,7 @@ func (p *passthroughUseCase) GetPortfolioAssets(ctx context.Context, address str
defer close(pooledBalancesChan)

// Channel to fetch unclaimed rewards concurrently.
unclaimedRewardsChan := make(chan coinsResult)
unclaimedRewardsChan := make(chan coinsResult, unclaimedRewardsNumJobs)
defer close(unclaimedRewardsChan)

go func() {
Expand Down Expand Up @@ -168,6 +173,17 @@ func (p *passthroughUseCase) GetPortfolioAssets(ctx context.Context, address str
}
}()

go func() {
// Fetch unclaimed staking rewards concurrently
unclaimedStakingRewards, err := p.passthroughGRPCClient.DelegationRewards(ctx, address)

// Send unclaimed rewards to the unclaimed rewards channel
unclaimedRewardsChan <- coinsResult{
coins: unclaimedStakingRewards,
err: err,
}
}()

// Aggregate poold coins callback
getPooledCoins := func(ctx context.Context, address string) (sdk.Coins, error) {
pooledCoins := sdk.Coins{}
Expand Down Expand Up @@ -202,8 +218,29 @@ func (p *passthroughUseCase) GetPortfolioAssets(ctx context.Context, address str

// Callback to fetch unclaimed rewards concurrently.
getUnclaimedRewards := func(ctx context.Context, address string) (sdk.Coins, error) {
unclaimedRewardsResult := <-unclaimedRewardsChan
return unclaimedRewardsResult.coins, unclaimedRewardsResult.err
unclaimedCoins := sdk.Coins{}

var finalErr error
for i := 0; i < unclaimedRewardsNumJobs; i++ {
unclaimedRewardsResult := <-unclaimedRewardsChan

if unclaimedRewardsResult.err != nil {
// Rather than returning the error, log it and continue
finalErr = unclaimedRewardsResult.err

// Ensure that coins are valid to be added and avoid panic.
if len(unclaimedRewardsResult.coins) > 0 && !unclaimedRewardsResult.coins.IsAnyNil() {
unclaimedCoins = unclaimedCoins.Add(unclaimedRewardsResult.coins...)
}

continue
}

unclaimedCoins = unclaimedCoins.Add(unclaimedRewardsResult.coins...)
}

// Return error and best-effort result
return unclaimedCoins, finalErr
}

// Fetch jobs to fetch the portfolio assets concurrently in separate gorooutines.
Expand Down
12 changes: 8 additions & 4 deletions passthrough/usecase/passthrough_usecase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ func (s *PassthroughUseCaseTestSuite) TestGetPotrfolioAssets_HappyPath() {
// Return error to test the silent error handling.
return sdk.NewCoins(wbtcCoin), sdk.NewCoins(invalidCoin), miscError
},
MockDelegationRewardsCb: func(ctx context.Context, address string) (sdk.Coins, error) {
// Return error to test the silent error handling.
return sdk.NewCoins(osmoCoin), miscError
},
}

// Initialize pools use case mock
Expand Down Expand Up @@ -213,11 +217,11 @@ func (s *PassthroughUseCaseTestSuite) TestGetPotrfolioAssets_HappyPath() {
IsBestEffort: true,
},
usecase.UnclaimedRewardsAssetsCategoryName: {
Capitalization: zero,
Capitalization: osmoCapitalization,
IsBestEffort: true,
},
usecase.TotalAssetsCategoryName: {
Capitalization: osmoCapitalization.Add(osmoCapitalization).Add(osmoCapitalization).Add(atomCapitalization).Add(wbtcCapitalization),
Capitalization: osmoCapitalization.Add(osmoCapitalization).Add(osmoCapitalization).Add(atomCapitalization).Add(wbtcCapitalization).Add(osmoCapitalization),
AccountCoinsResult: []passthroughdomain.AccountCoinsResult{
{
Coin: atomCoin,
Expand All @@ -232,8 +236,8 @@ func (s *PassthroughUseCaseTestSuite) TestGetPotrfolioAssets_HappyPath() {
CapitalizationValue: zero,
},
{
Coin: osmoCoin.Add(osmoCoin).Add(osmoCoin),
CapitalizationValue: osmoCapitalization.Add(osmoCapitalization).Add(osmoCapitalization),
Coin: osmoCoin.Add(osmoCoin).Add(osmoCoin).Add(osmoCoin),
CapitalizationValue: osmoCapitalization.Add(osmoCapitalization).Add(osmoCapitalization).Add(osmoCapitalization),
},
},
IsBestEffort: true,
Expand Down

0 comments on commit b469f69

Please sign in to comment.