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(oracle): add support for get price indexed by assetID #77

Merged
merged 6 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ func NewExocoreApp(
bApp.CreateQueryContext,
app.AssetsKeeper,
&app.DelegationKeeper, // intentionally a pointer, since not yet initialized.
operatorTypes.MockOracle{},
&app.OracleKeeper,
operatorTypes.MockAVS{AssetsKeeper: app.AssetsKeeper},
delegationTypes.VirtualSlashKeeper{},
)
Expand Down
20 changes: 20 additions & 0 deletions testutil/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,28 @@ func (suite *BaseTestSuite) SetupWithGenesisValSet(genAccs []authtypes.GenesisAc
}
// x/oracle initialization
oracleDefaultParams := oracletypes.DefaultParams()
oracleDefaultParams.Tokens[1].AssetID = "0xdac17f958d2ee523a2206206994597c13d831ec7_0x65"
oracleDefaultParams.TokenFeeders[1].StartBaseBlock = 1
oracleDefaultParams.Tokens = append(oracleDefaultParams.Tokens, &oracletypes.Token{
Name: "USDT",
ChainID: 1,
ContractAddress: "0x",
Decimal: 0,
Active: true,
AssetID: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48_0x65",
})
oracleDefaultParams.TokenFeeders = append(oracleDefaultParams.TokenFeeders, &oracletypes.TokenFeeder{
TokenID: 2,
RuleID: 1,
StartRoundID: 1,
StartBaseBlock: 1,
Interval: 10,
})
oracleGenesis := oracletypes.NewGenesisState(oracleDefaultParams)
oracleGenesis.PricesList = []oracletypes.Prices{
{TokenID: 1, NextRoundID: 2, PriceList: []*oracletypes.PriceTimeRound{{Price: "1", Decimal: 0, RoundID: 1}}},
{TokenID: 2, NextRoundID: 2, PriceList: []*oracletypes.PriceTimeRound{{Price: "1", Decimal: 0, RoundID: 1}}},
}
genesisState[oracletypes.ModuleName] = app.AppCodec().MustMarshalJSON(oracleGenesis)

assetsGenesis := assetstypes.NewGenesis(
Expand Down
21 changes: 17 additions & 4 deletions x/operator/keeper/abci.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package keeper

import (
"errors"

sdkmath "cosmossdk.io/math"
assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types"
delegationkeeper "github.com/ExocoreNetwork/exocore/x/delegation/keeper"
operatortypes "github.com/ExocoreNetwork/exocore/x/operator/types"
oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types"
abci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"golang.org/x/xerrors"
Expand All @@ -30,7 +33,7 @@ func (k *Keeper) CalculateUSDValueForOperator(
operator string,
assetsFilter map[string]interface{},
decimals map[string]uint32,
prices map[string]operatortypes.Price,
prices map[string]oracletypes.Price,
) (operatortypes.OperatorUSDValue, error) {
var err error
ret := operatortypes.OperatorUSDValue{
Expand All @@ -40,14 +43,19 @@ func (k *Keeper) CalculateUSDValueForOperator(
}
// iterate all assets owned by the operator to calculate its voting power
opFuncToIterateAssets := func(assetID string, state *assetstypes.OperatorAssetInfo) error {
var price operatortypes.Price
// var price operatortypes.Price
var price oracletypes.Price
var decimal uint32
if isForSlash {
// when calculated the USD value for slashing, the input prices map is null
// so the price needs to be retrieved here
price, err = k.oracleKeeper.GetSpecifiedAssetsPrice(ctx, assetID)
if err != nil {
return err
// TODO: when assetID is not registered in oracle module, this error will finally lead to panic
if !errors.Is(err, oracletypes.ErrGetPriceRoundNotFound) {
return err
}
// TODO: for now, we ignore the error when the price round is not found and set the price to 1 to avoid panic
}
assetInfo, err := k.assetsKeeper.GetStakingAssetInfo(ctx, assetID)
if err != nil {
Expand Down Expand Up @@ -96,8 +104,13 @@ func (k *Keeper) UpdateVotingPower(ctx sdk.Context, avsAddr string) error {
return err
}
prices, err := k.oracleKeeper.GetMultipleAssetsPrices(ctx, assets)
// TODO: for now, we ignore the error when the price round is not found and set the price to 1 to avoid panic
if err != nil {
return err
// TODO: when assetID is not registered in oracle module, this error will finally lead to panic
if !errors.Is(err, oracletypes.ErrGetPriceRoundNotFound) {
return err
}
// TODO: for now, we ignore the error when the price round is not found and set the price to 1 to avoid panic
}
// update the voting power of operators and AVS
avsVotingPower := sdkmath.LegacyNewDec(0)
Expand Down
27 changes: 8 additions & 19 deletions x/operator/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
assetstype "github.com/ExocoreNetwork/exocore/x/assets/types"
"github.com/ExocoreNetwork/exocore/x/delegation/keeper"
delegationtype "github.com/ExocoreNetwork/exocore/x/delegation/types"
oracletype "github.com/ExocoreNetwork/exocore/x/oracle/types"
tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -64,37 +65,25 @@ type Price struct {
type OracleKeeper interface {
// GetSpecifiedAssetsPrice is a function to retrieve the asset price according to the
// assetID.
GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (Price, error)
GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (oracletype.Price, error)
// GetMultipleAssetsPrices is a function to retrieve multiple assets prices according to the
// assetID.
GetMultipleAssetsPrices(ctx sdk.Context, assets map[string]interface{}) (map[string]Price, error)
GetMultipleAssetsPrices(ctx sdk.Context, assets map[string]interface{}) (map[string]oracletype.Price, error)
}

type MockOracle struct{}

func (MockOracle) GetSpecifiedAssetsPrice(_ sdk.Context, _ string) (Price, error) {
return Price{
func (MockOracle) GetSpecifiedAssetsPrice(_ sdk.Context, _ string) (oracletype.Price, error) {
return oracletype.Price{
Value: sdkmath.NewInt(1),
Decimal: 0,
}, nil
}

func (MockOracle) GetPriceChangeAssets(_ sdk.Context) (map[string]*PriceChange, error) {
// use USDT as the mock asset
ret := make(map[string]*PriceChange, 0)
usdtAssetID := "0xdac17f958d2ee523a2206206994597c13d831ec7_0x65"
ret[usdtAssetID] = &PriceChange{
NewPrice: sdkmath.NewInt(1),
OriginalPrice: sdkmath.NewInt(1),
Decimal: 0,
}
return nil, nil
}

func (MockOracle) GetMultipleAssetsPrices(_ sdk.Context, assets map[string]interface{}) (map[string]Price, error) {
ret := make(map[string]Price, 0)
func (MockOracle) GetMultipleAssetsPrices(_ sdk.Context, assets map[string]interface{}) (map[string]oracletype.Price, error) {
ret := make(map[string]oracletype.Price, 0)
for assetID := range assets {
ret[assetID] = Price{
ret[assetID] = oracletype.Price{
Value: sdkmath.NewInt(1),
Decimal: 0,
}
Expand Down
23 changes: 20 additions & 3 deletions x/oracle/keeper/aggregator/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type roundInfo struct {
// AggregatorContext keeps memory cache for state params, validatorset, and updatedthese values as they updated on chain. And it keeps the information to track all tokenFeeders' status and data collection
// nolint
type AggregatorContext struct {
params *common.Params
params *types.Params

// validator->power
validatorsPower map[string]*big.Int
Expand Down Expand Up @@ -221,7 +221,7 @@ func (agc *AggregatorContext) SealRound(ctx sdk.Context, force bool) (success []
return success, failed
}

// TODO: test to remove PrepareRound into BeginBlock
// PrepareBeginBlock is called at BeginBlock stage, to prepare the roundInfo for the current block
func (agc *AggregatorContext) PrepareRoundBeginBlock(ctx sdk.Context, block uint64) {
// block>0 means recache initialization, all roundInfo is empty
if block == 0 {
Expand Down Expand Up @@ -274,10 +274,12 @@ func (agc *AggregatorContext) PrepareRoundBeginBlock(ctx sdk.Context, block uint
}
}

func (agc *AggregatorContext) SetParams(p *common.Params) {
// SetParams sets the params field of aggregatorContext“
func (agc *AggregatorContext) SetParams(p *types.Params) {
agc.params = p
}

// SetValidatorPowers sets the map of validator's power for aggreagtorContext
func (agc *AggregatorContext) SetValidatorPowers(vp map[string]*big.Int) {
// t := big.NewInt(0)
agc.totalPower = big.NewInt(0)
Expand All @@ -288,10 +290,25 @@ func (agc *AggregatorContext) SetValidatorPowers(vp map[string]*big.Int) {
}
}

// GetValidatorPowers returns the map of validator's power stored in aggregatorContext
func (agc *AggregatorContext) GetValidatorPowers() (vp map[string]*big.Int) {
return agc.validatorsPower
}

// GetTokenIDFromAssetID returns tokenID for corresponding tokenID, it returns 0 if agc.params is nil or assetID not found in agc.params
func (agc *AggregatorContext) GetTokenIDFromAssetID(assetID string) int {
if agc.params == nil {
return 0
}
return agc.params.GetTokenIDFromAssetID(assetID)
}
Comment on lines +298 to +304
Copy link
Contributor

Choose a reason for hiding this comment

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

Improve error handling in GetTokenIDFromAssetID.

Instead of returning 0 when agc.params is nil or assetID is not found, consider returning an error to provide more context.

func (agc *AggregatorContext) GetTokenIDFromAssetID(assetID string) (int, error) {
	if agc.params == nil {
		return 0, errors.New("params are not set")
	}
	tokenID := agc.params.GetTokenIDFromAssetID(assetID)
	if tokenID == 0 {
		return 0, errors.New("assetID not found")
	}
	return tokenID, nil
}
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
// GetTokenIDFromAssetID returns tokenID for corresponding tokenID, it returns 0 if agc.params is nil or assetID not found in agc.params
func (agc *AggregatorContext) GetTokenIDFromAssetID(assetID string) int {
if agc.params == nil {
return 0
}
return agc.params.GetTokenIDFromAssetID(assetID)
}
func (agc *AggregatorContext) GetTokenIDFromAssetID(assetID string) (int, error) {
if agc.params == nil {
return 0, errors.New("params are not set")
}
tokenID := agc.params.GetTokenIDFromAssetID(assetID)
if tokenID == 0 {
return 0, errors.New("assetID not found")
}
return tokenID, nil
}


// GetParams returns the params field of aggregatorContext
func (agc *AggregatorContext) GetParams() types.Params {
return *agc.params
}

// NewAggregatorContext returns a new instance of AggregatorContext
func NewAggregatorContext() *AggregatorContext {
return &AggregatorContext{
validatorsPower: make(map[string]*big.Int),
Expand Down
3 changes: 1 addition & 2 deletions x/oracle/keeper/aggregator/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,9 @@ func initAggregatorContext4Test() *AggregatorContext {
}

p := defaultParams
pWrapped := common.Params(p)

agc.SetValidatorPowers(validatorPowers)
agc.SetParams(&pWrapped)
agc.SetParams(&p)
return agc
}

Expand Down
16 changes: 8 additions & 8 deletions x/oracle/keeper/cache/caches.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var zeroBig = big.NewInt(0)

type (
ItemV map[string]*big.Int
ItemP *common.Params
ItemP types.Params
ItemM types.MsgItem
)

Expand All @@ -22,7 +22,6 @@ type Cache struct {
params *cacheParams
}

// type cacheMsgs map[uint64][]*ItemM
type cacheMsgs []*ItemM

// used to track validator change
Expand All @@ -33,7 +32,8 @@ type cacheValidator struct {

// used to track params change
type cacheParams struct {
params *common.Params
// params types.Params
params *ItemP
update bool
}

Expand Down Expand Up @@ -102,10 +102,10 @@ func (c *cacheValidator) commit(ctx sdk.Context, k common.KeeperOracle) {
k.SetValidatorUpdateBlock(ctx, types.ValidatorUpdateBlock{Block: block})
}

func (c *cacheParams) add(p *common.Params) {
func (c *cacheParams) add(p ItemP) {
// params' update is triggered when params is actually updated, so no need to do comparison here, just udpate and mark the flag
// TODO: add comparison check, that's something should be done for validation
c.params = p
c.params = &p
Comment on lines +105 to +108
Copy link
Contributor

Choose a reason for hiding this comment

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

Add validation check for parameter updates.

The function updates the parameters without comparison, which might lead to unnecessary updates. Consider adding a validation check to ensure that the parameters have actually changed.

-  // TODO: add comparison check, that's something should be done for validation
-  c.params = &p
+  if !reflect.DeepEqual(c.params, &p) {
+    c.params = &p
+    c.update = true
+  }
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
func (c *cacheParams) add(p ItemP) {
// params' update is triggered when params is actually updated, so no need to do comparison here, just udpate and mark the flag
// TODO: add comparison check, that's something should be done for validation
c.params = p
c.params = &p
func (c *cacheParams) add(p ItemP) {
// params' update is triggered when params is actually updated, so no need to do comparison here, just udpate and mark the flag
if !reflect.DeepEqual(c.params, &p) {
c.params = &p
c.update = true
}

c.update = true
}

Expand Down Expand Up @@ -164,11 +164,11 @@ func (c *Cache) GetCache(i any) bool {
item[addr] = power
}
return c.validators.update
case ItemP:
case *ItemP:
if item == nil {
return false
}
*item = *(c.params.params)
*item = *c.params.params
return c.params.update
case *([]*ItemM):
if item == nil {
Expand Down Expand Up @@ -218,7 +218,7 @@ func NewCache() *Cache {
validators: make(map[string]*big.Int),
},
params: &cacheParams{
params: &common.Params{},
params: &ItemP{},
},
}
}
9 changes: 4 additions & 5 deletions x/oracle/keeper/cache/caches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"math/big"
"testing"

"github.com/ExocoreNetwork/exocore/x/oracle/keeper/common"
"github.com/ExocoreNetwork/exocore/x/oracle/types"
. "github.com/smartystreets/goconvey/convey"
// "go.uber.org/mock/gomock"
Expand All @@ -13,7 +12,7 @@ import (
func TestCache(t *testing.T) {
c := NewCache()
p := defaultParams
pWrapped := common.Params(p)
pWrapped := ItemP(p)

// ctrl := gomock.NewController(t)
// defer ctrl.Finish()
Expand All @@ -22,9 +21,9 @@ func TestCache(t *testing.T) {

Convey("test cache", t, func() {
Convey("add pramams item", func() {
c.AddCache(ItemP(&pWrapped))
pReturn := &common.Params{}
c.GetCache(ItemP(pReturn))
c.AddCache(pWrapped)
pReturn := &ItemP{}
c.GetCache(pReturn)
So(*pReturn, ShouldResemble, pWrapped)
})

Expand Down
11 changes: 9 additions & 2 deletions x/oracle/keeper/common/expected_keepers.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package common

import (
"cosmossdk.io/math"
sdkmath "cosmossdk.io/math"
dogfoodkeeper "github.com/ExocoreNetwork/exocore/x/dogfood/keeper"
dogfoodtypes "github.com/ExocoreNetwork/exocore/x/dogfood/types"
"github.com/ExocoreNetwork/exocore/x/oracle/types"
Expand All @@ -10,6 +10,11 @@ import (
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

type Price struct {
Value sdkmath.Int
Decimal uint8
}

type KeeperOracle interface {
KeeperDogfood

Expand All @@ -33,12 +38,14 @@ type KeeperOracle interface {

RemoveRecentParams(sdk.Context, uint64)
RemoveRecentMsg(sdk.Context, uint64)

GetMultipleAssetsPrices(ctx sdk.Context, assets map[string]interface{}) (map[string]types.Price, error)
}

var _ KeeperDogfood = dogfoodkeeper.Keeper{}

type KeeperDogfood = interface {
GetLastTotalPower(ctx sdk.Context) math.Int
GetLastTotalPower(ctx sdk.Context) sdkmath.Int
IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator stakingTypes.ValidatorI) (stop bool))
GetValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate
GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingTypes.Validator, found bool)
Expand Down
Loading
Loading