Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
4 changes: 2 additions & 2 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,7 @@ github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwm
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
Expand Down Expand Up @@ -1250,8 +1251,6 @@ github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/informalsystems/tm-load-test v1.3.0 h1:FGjKy7vBw6mXNakt+wmNWKggQZRsKkEYpaFk/zR64VA=
github.com/informalsystems/tm-load-test v1.3.0/go.mod h1:OQ5AQ9TbT5hKWBNIwsMjn6Bf4O0U4b1kRc+0qZlQJKw=
github.com/ingenuity-build/multierror v0.1.0 h1:nS+YE/+ujwtl09kNWsIMNu8TUjs9PKrtnP5x45txxOs=
github.com/ingenuity-build/multierror v0.1.0/go.mod h1:ZU6BORie0gkg/hhdTvEH979RsaCE2Wdsxph9AISVmac=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
Expand Down Expand Up @@ -2284,6 +2283,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.
google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250127172529-29210b9bc287/go.mod h1:7VGktjvijnuhf2AobFqsoaBGnG8rImcxqoL+QPBPRq4=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250219182151-9fdb1cabc7b2 h1:UZtupsOaDeUm4KiG4HQTSyENUuCayW8K5d5cs7zK79c=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:35wIojE/F1ptq1nfNDNjtowabHoMSA2qQs7+smpCO5s=
Expand Down
16 changes: 10 additions & 6 deletions xcclookup/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@ chains:
archway-1: https://archway-1.rpc.quicksilver.zone:443
omniflixhub-1: https://omniflixhub-1.rpc.quicksilver.zone:443
injective-1: https://injective-1.rpc.quicksilver.zone:443
phoenix-1: https://phoenix-1.rpc.quicksilver.zone:443
centauri-1: https://centauri-1.rpc.quicksilver.zone:443

mocks:
membrane_params:
- contractaddress: osmo1gy5gpqqlth0jpm9ydxlmff6g5mpnfvrfxd3mfc8dhyt03waumtzqt8exxr
connections:
- connectionid: connection-2
chainid: osmosis-1
lastepoch: 39990000
prefix: osmo
transferchannel: channel-2
# umee_params:
# - chainid: umee-1
# connections:
# - connectionid: connection-2
# chainid: osmosis-1
# lastepoch: 39990000
# prefix: osmo
# transferchannel: channel-2
# osmosis_pools:
# - poolid: 1059
# poolname: qOSMO/OSMO
Expand Down
250 changes: 250 additions & 0 deletions xcclookup/pkgs/claims/membrane.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package claims

import (
"context"
"encoding/json"
"fmt"
"time"

"cosmossdk.io/math"

cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"

rpcclient "github.com/cometbft/cometbft/rpc/client"

"github.com/quicksilver-zone/quicksilver/utils/addressutils"
cmtypes "github.com/quicksilver-zone/quicksilver/x/claimsmanager/types"
icstypes "github.com/quicksilver-zone/quicksilver/x/interchainstaking/types"
prewards "github.com/quicksilver-zone/quicksilver/x/participationrewards/types"

"github.com/quicksilver-zone/quicksilver/xcclookup/pkgs/logger"
"github.com/quicksilver-zone/quicksilver/xcclookup/pkgs/types"
)

// MembranePosition represents a position in the Membrane protocol
type MembranePosition struct {
PositionID math.Int `json:"position_id"`
CollateralAssets []MembraneCollateralAsset `json:"collateral_assets"`
CreditAmount math.Int `json:"credit_amount"`
}

// MembraneCollateralAsset represents a collateral asset in a Membrane position
type MembraneCollateralAsset struct {
Asset MembraneCollateralAssetAsset `json:"asset"`
MaxBorrowLTV sdk.Dec `json:"max_borrow_LTV"`
MaxLTV sdk.Dec `json:"max_LTV"`
RateIndex sdk.Dec `json:"rate_index"`
}

// MembraneCollateralAssetAsset represents the asset information in a collateral asset
type MembraneCollateralAssetAsset struct {
Info MembraneCollateralAssetInfo `json:"info"`
Amount math.Int `json:"amount"`
}

// MembraneCollateralAssetInfo represents the info field in a collateral asset
type MembraneCollateralAssetInfo struct {
NativeToken MembraneNativeToken `json:"native_token"`
}

// MembraneNativeToken represents a native token
type MembraneNativeToken struct {
Denom string `json:"denom"`
}

// MembraneClaim performs the reverse operation of the participation rewards module
// It queries the Membrane contract for user positions and validates them against
// the allowed liquid tokens for the Osmosis chain
func MembraneClaim(
ctx context.Context,
cfg types.Config,
cacheMgr *types.CacheManager,
address string,
submitAddress string,
chain string,
height int64,
) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error) {
log := logger.FromContext(ctx)

// Get cached data
membraneParamsCache, err := types.GetCache[prewards.MembraneProtocolData](ctx, cacheMgr)
if err != nil {
return nil, nil, fmt.Errorf("failed to get membrane params cache: %w", err)
}

osmosisParamsCache, err := types.GetCache[prewards.OsmosisParamsProtocolData](ctx, cacheMgr)
if err != nil {
return nil, nil, fmt.Errorf("failed to get osmosis params cache: %w", err)
}

liquidAllowedDenomsCache, err := types.GetCache[prewards.LiquidAllowedDenomProtocolData](ctx, cacheMgr)
if err != nil {
return nil, nil, fmt.Errorf("failed to get liquid allowed denoms cache: %w", err)
}

zoneCache, err := types.GetCache[icstypes.Zone](ctx, cacheMgr)
if err != nil {
return nil, nil, fmt.Errorf("failed to get zone cache: %w", err)
}

// Find the membrane params for the chain
var membraneParams prewards.MembraneProtocolData
found := false
for _, params := range membraneParamsCache {
// For now, we assume there's only one membrane params entry
// In the future, we might need to match by chain ID
membraneParams = params
found = true
break
}

if !found {
log.Warn("No membrane params found in cache")
return nil, nil, nil
}

// Find the osmosis params for the chain
found = false
for _, params := range osmosisParamsCache {
if params.ChainID == chain {
found = true
break
}
}

if !found {
log.Warn("No osmosis params found for chain", "chain", chain)
return nil, nil, nil
}

// Get the host endpoint for the chain
host, ok := cfg.Chains[chain]
if !ok {
log.Warn("No endpoint found for chain", "chain", chain)
return nil, nil, nil
}

// Create RPC client
client, err := types.NewRPCClient(host, time.Duration(cfg.Timeout)*time.Second)
if err != nil {
return nil, nil, fmt.Errorf("failed to create RPC client: %w", err)
}

// Setup codec
interfaceRegistry := cdctypes.NewInterfaceRegistry()
cmtypes.RegisterInterfaces(interfaceRegistry)

// Convert address to Osmosis format
addrBytes, err := addressutils.AccAddressFromBech32(address, "")
if err != nil {
return nil, nil, fmt.Errorf("invalid address: %w", err)
}

osmoAddress, err := addressutils.EncodeAddressToBech32("osmo", addrBytes)
if err != nil {
return nil, nil, fmt.Errorf("failed to encode address: %w", err)
}

// Get mapped address if it exists
mappedAddress := osmoAddress
// Note: Address mapping is handled by the assets service through GetMappedAddresses
// For now, we'll just use the original address and let the assets service handle mapping

// Query both the original address and mapped address
addresses := []string{osmoAddress}
if mappedAddress != osmoAddress {
addresses = append(addresses, mappedAddress)
}

msg := map[string]prewards.MsgSubmitClaim{}
assets := map[string]sdk.Coins{}

// Process each address
for _, addr := range addresses {
// Generate the key for the membrane contract query using the proper key format
// The key should be a CW namespaced key: 0x03 + contract_address + 0x00 + "positions" + user_address
contractAddrBytes, err := addressutils.AccAddressFromBech32(membraneParams.ContractAddress, "osmo")
if err != nil {
log.Debug("Invalid membrane contract address", "address", membraneParams.ContractAddress, "error", err)
continue
}

// Build the CW namespaced key
key := make([]byte, 0)
key = append(key, 0x03) // prefix
key = append(key, contractAddrBytes...) // contract address
key = append(key, 0x00) // null terminator
key = append(key, byte(len("positions"))) // length of "positions"
key = append(key, []byte("positions")...) // "positions"
key = append(key, []byte(addr)...) // user address

// Query the membrane contract
abciquery, err := client.ABCIQueryWithOptions(
ctx,
"/store/wasm/key",
key,
rpcclient.ABCIQueryOptions{Height: height, Prove: true},
)
if err != nil {
log.Debug("Failed to query membrane contract", "address", addr, "error", err)
continue
}

// Parse the response
var positions []MembranePosition
if err := json.Unmarshal(abciquery.Response.Value, &positions); err != nil {
log.Debug("Failed to unmarshal membrane positions", "address", addr, "error", err)
continue
}

// Process each position
for _, position := range positions {
for _, collateralAsset := range position.CollateralAssets {
// Check if this denom is in the allowed liquid tokens
for _, liquidToken := range liquidAllowedDenomsCache {
if liquidToken.ChainID == chain && liquidToken.IbcDenom == collateralAsset.Asset.Info.NativeToken.Denom {
// Find the corresponding zone
for _, zone := range zoneCache {
if zone.ChainId == liquidToken.RegisteredZoneChainID {
// Create or update the claim message
if _, ok := msg[zone.ChainId]; !ok {
msg[zone.ChainId] = prewards.MsgSubmitClaim{
UserAddress: submitAddress,
Zone: zone.ChainId,
SrcZone: chain,
ClaimType: cmtypes.ClaimTypeMembrane,
Proofs: make([]*cmtypes.Proof, 0),
}
}

// Add the proof
proof := cmtypes.Proof{
Data: abciquery.Response.Value,
Key: abciquery.Response.Key,
ProofOps: abciquery.Response.ProofOps,
Height: abciquery.Response.Height,
ProofType: "membrane",
}

chainMsg := msg[zone.ChainId]
chainMsg.Proofs = append(chainMsg.Proofs, &proof)
msg[zone.ChainId] = chainMsg

// Add to assets
coin := sdk.NewCoin(liquidToken.QAssetDenom, collateralAsset.Asset.Amount)
assets[chain] = assets[chain].Add(coin)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix asset accumulation to use zone chain ID instead of source chain.

The assets are being accumulated under the source chain key, but they should be accumulated per zone chain ID to match the claim messages structure.

 // Add to assets
 coin := sdk.NewCoin(liquidToken.QAssetDenom, collateralAsset.Asset.Amount)
-assets[chain] = assets[chain].Add(coin)
+assets[zone.ChainId] = assets[zone.ChainId].Add(coin)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Add to assets
coin := sdk.NewCoin(liquidToken.QAssetDenom, collateralAsset.Asset.Amount)
assets[chain] = assets[chain].Add(coin)
// Add to assets
coin := sdk.NewCoin(liquidToken.QAssetDenom, collateralAsset.Asset.Amount)
assets[zone.ChainId] = assets[zone.ChainId].Add(coin)
🤖 Prompt for AI Agents
In xcclookup/pkgs/claims/membrane.go around lines 234 to 236, the assets map is
currently keyed by the source chain ID, but it should use the zone chain ID to
correctly accumulate assets per zone. Update the key used in the assets map from
the source chain variable to the zone chain ID variable to align with the claim
messages structure.


break
}
}
break
}
}
}
}
}

log.Debug("Membrane claim processing completed", "address", address, "chain", chain, "positions_count", len(msg))
return msg, assets, nil
}
9 changes: 9 additions & 0 deletions xcclookup/pkgs/mocks/mock_cache_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type MockCacheManager struct {
GetOsmosisClPoolsFunc func(ctx context.Context) ([]prewards.OsmosisClPoolProtocolData, error)
GetLiquidAllowedDenomsFunc func(ctx context.Context) ([]prewards.LiquidAllowedDenomProtocolData, error)
GetUmeeParamsFunc func(ctx context.Context) ([]prewards.UmeeParamsProtocolData, error)
GetMembraneParamsFunc func(ctx context.Context) ([]prewards.MembraneProtocolData, error)
GetZonesFunc func(ctx context.Context) ([]icstypes.Zone, error)
AddMocksFunc func(ctx context.Context, mocks interface{}) error
}
Expand Down Expand Up @@ -69,6 +70,14 @@ func (m *MockCacheManager) GetUmeeParams(ctx context.Context) ([]prewards.UmeePa
return make([]prewards.UmeeParamsProtocolData, 0), nil
}

// GetMembraneParams calls the mock function
func (m *MockCacheManager) GetMembraneParams(ctx context.Context) ([]prewards.MembraneProtocolData, error) {
if m.GetMembraneParamsFunc != nil {
return m.GetMembraneParamsFunc(ctx)
}
return make([]prewards.MembraneProtocolData, 0), nil
}

// GetZones calls the mock function
func (m *MockCacheManager) GetZones(ctx context.Context) ([]icstypes.Zone, error) {
if m.GetZonesFunc != nil {
Expand Down
15 changes: 12 additions & 3 deletions xcclookup/pkgs/mocks/mock_claims_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import (

// MockClaimsService is a mock implementation of ClaimsServiceInterface
type MockClaimsService struct {
OsmosisClaimFunc func(ctx context.Context, address, submitAddress, chain string, height int64) (types.OsmosisResult, error)
UmeeClaimFunc func(ctx context.Context, address, submitAddress, chain string, height int64) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error)
LiquidClaimFunc func(ctx context.Context, address, submitAddress string, connection prewards.ConnectionProtocolData, height int64) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error)
OsmosisClaimFunc func(ctx context.Context, address, submitAddress, chain string, height int64) (types.OsmosisResult, error)
UmeeClaimFunc func(ctx context.Context, address, submitAddress, chain string, height int64) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error)
LiquidClaimFunc func(ctx context.Context, address, submitAddress string, connection prewards.ConnectionProtocolData, height int64) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error)
MembraneClaimFunc func(ctx context.Context, address, submitAddress, chain string, height int64) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error)
}

// OsmosisClaim calls the mock function
Expand All @@ -41,5 +42,13 @@ func (m *MockClaimsService) LiquidClaim(ctx context.Context, address, submitAddr
return make(map[string]prewards.MsgSubmitClaim), make(map[string]sdk.Coins), nil
}

// MembraneClaim calls the mock function
func (m *MockClaimsService) MembraneClaim(ctx context.Context, address, submitAddress, chain string, height int64) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error) {
if m.MembraneClaimFunc != nil {
return m.MembraneClaimFunc(ctx, address, submitAddress, chain, height)
}
return make(map[string]prewards.MsgSubmitClaim), make(map[string]sdk.Coins), nil
}

// Ensure MockClaimsService implements ClaimsServiceInterface
var _ types.ClaimsServiceInterface = &MockClaimsService{}
Loading
Loading