Skip to content

Commit

Permalink
Merge pull request #109 from onomyprotocol/dong/check_time_price
Browse files Browse the repository at this point in the history
Oracle: check allowed price delay
  • Loading branch information
DongLieu authored Dec 9, 2024
2 parents 62c33c0 + 8b0d155 commit 3e24d9a
Show file tree
Hide file tree
Showing 18 changed files with 311 additions and 173 deletions.
2 changes: 1 addition & 1 deletion proto/reserve/oracle/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ message BandPriceState {
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false
];
uint64 resolve_time = 3;
int64 resolve_time = 3;
uint64 request_ID = 4;
PriceState price_state = 5 [ (gogoproto.nullable) = false ];
}
Expand Down
4 changes: 4 additions & 0 deletions proto/reserve/oracle/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package reserve.oracle;

import "amino/amino.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/duration.proto";

option go_package = "github.com/onomyprotocol/reserve/x/oracle/types";

// Params defines the parameters for the module.
message Params {
option (amino.name) = "reserve/x/oracle/Params";
option (gogoproto.equal) = true;

google.protobuf.Duration allowed_price_delay = 1
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true];
}
32 changes: 21 additions & 11 deletions x/oracle/keeper/band_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,33 +311,43 @@ func (k Keeper) AddNewSymbolToBandOracleRequest(ctx context.Context, symbol stri
}

// GetPrice fetches band ibc prices for a given pair in math.LegacyDec
func (k Keeper) GetPrice(ctx context.Context, base, quote string) *math.LegacyDec {
func (k Keeper) GetPrice(ctx context.Context, base, quote string) (price math.LegacyDec, err error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)

allowedPriceDelay := k.GetParams(ctx).AllowedPriceDelay
// query ref by using GetBandPriceState
basePriceState := k.GetBandPriceState(ctx, base)
if basePriceState == nil || basePriceState.Rate.IsZero() {
k.Logger(ctx).Info(fmt.Sprintf("Can not get price state of base denom %s: price state is nil or rate is zero", base))
return nil
err = fmt.Errorf("can not get price state of base denom %s: price state is nil or rate is zero", base)
k.Logger(ctx).Info(err.Error())
return price, err
}
if sdkCtx.BlockTime().Sub(time.Unix(basePriceState.ResolveTime, 0)) > allowedPriceDelay {
return price, fmt.Errorf("symbol %s old price state", base)
}

if quote == types.QuoteUSD || quote == vaultstypes.DefaultMintDenoms[0] {
return &basePriceState.PriceState.Price
return basePriceState.PriceState.Price, nil
}

quotePriceState := k.GetBandPriceState(ctx, quote)
if quotePriceState == nil || quotePriceState.Rate.IsZero() {
k.Logger(ctx).Info(fmt.Sprintf("Can not get price state of quote denom %s: price state is nil or rate is zero", quote))
return nil
err = fmt.Errorf("can not get price state of base denom %s: price state is nil or rate is zero", quote)
k.Logger(ctx).Info(err.Error())
return price, err
}
if sdkCtx.BlockTime().Sub(time.Unix(quotePriceState.ResolveTime, 0)) > allowedPriceDelay {
return price, fmt.Errorf("symbol %s old price state", quote)
}

baseRate := basePriceState.Rate.ToLegacyDec()
quoteRate := quotePriceState.Rate.ToLegacyDec()

if baseRate.IsNil() || quoteRate.IsNil() || !baseRate.IsPositive() || !quoteRate.IsPositive() {
return nil
return price, fmt.Errorf("get price error validate for baseRate %s(%s) or quoteRate %s(%s)", base, baseRate.String(), quote, quoteRate.String())
}

price := baseRate.Quo(quoteRate)
return &price
price = baseRate.Quo(quoteRate)
return price, nil
}

// RequestBandOraclePrices creates and sends an IBC packet to fetch band oracle price feed data through IBC.
Expand Down Expand Up @@ -464,7 +474,7 @@ func (k *Keeper) updateBandPriceStates(
var (
inputSymbols = input.PriceSymbols()
requestID = packet.RequestID
resolveTime = uint64(packet.ResolveTime)
resolveTime = packet.ResolveTime
symbols = make([]string, 0, len(inputSymbols))
prices = make([]math.LegacyDec, 0, len(inputSymbols))
)
Expand Down
90 changes: 70 additions & 20 deletions x/oracle/keeper/band_oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,28 @@ func TestBandPriceState(t *testing.T) {
states := app.OracleKeeper.GetAllBandPriceStates(ctx)
require.Equal(t, 0, len(states))

price := app.OracleKeeper.GetPrice(ctx, "ATOM", "USD")
require.Nil(t, price)
_, err := app.OracleKeeper.GetPrice(ctx, "ATOM", "USD")
require.Error(t, err)

bandPriceState := &types.BandPriceState{
Symbol: "ATOM",
Rate: math.NewInt(10),
ResolveTime: 1,
ResolveTime: time.Now().Unix(),
Request_ID: 1,
PriceState: *types.NewPriceState(math.LegacyNewDec(10), 1),
}
// set band price state for ATOM
err := app.OracleKeeper.SetBandPriceState(ctx, "ATOM", bandPriceState)
err = app.OracleKeeper.SetBandPriceState(ctx, "ATOM", bandPriceState)
require.NoError(t, err)

data = app.OracleKeeper.GetBandPriceState(ctx, "ATOM")
require.Equal(t, bandPriceState, data)

price = app.OracleKeeper.GetPrice(ctx, "ATOM", "USD")
price, err := app.OracleKeeper.GetPrice(ctx, "ATOM", "USD")
require.NoError(t, err)

expect := math.LegacyNewDec(10)
require.Equal(t, &expect, price)
require.Equal(t, expect, price)

states = app.OracleKeeper.GetAllBandPriceStates(ctx)
require.Equal(t, 1, len(states))
Expand Down Expand Up @@ -166,28 +168,28 @@ func TestGetPrice(t *testing.T) {
bandPriceStateATOM := &types.BandPriceState{
Symbol: "ATOM",
Rate: math.NewInt(10),
ResolveTime: 1,
ResolveTime: time.Now().Unix(),
Request_ID: 1,
PriceState: *types.NewPriceState(math.LegacyNewDec(10), 1),
}
bandPriceStateUSD := &types.BandPriceState{
Symbol: "USD",
Rate: math.NewInt(1),
ResolveTime: 1,
ResolveTime: time.Now().Unix(),
Request_ID: 1,
PriceState: *types.NewPriceState(math.LegacyNewDec(1), 1),
}
bandPriceStateNOM := &types.BandPriceState{
Symbol: "NOM",
Rate: math.NewInt(2),
ResolveTime: 1,
ResolveTime: time.Now().Unix(),
Request_ID: 1,
PriceState: *types.NewPriceState(math.LegacyNewDec(2), 1),
}
invalidPriceStateATOM := &types.BandPriceState{
Symbol: "ATOM",
Rate: math.NewInt(0), // Invalid base rate
ResolveTime: 1,
ResolveTime: time.Now().Unix(),
Request_ID: 1,
PriceState: *types.NewPriceState(math.LegacyNewDec(0), 1),
}
Expand All @@ -203,7 +205,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol string
basePriceState *types.BandPriceState
quotePriceState *types.BandPriceState
expectedPrice *math.LegacyDec
expectedPrice math.LegacyDec
expectNil bool
}{
// Return nil cases first
Expand All @@ -213,7 +215,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol: "USD",
basePriceState: nil,
quotePriceState: nil,
expectedPrice: nil,
expectedPrice: math.LegacyNewDec(-1),
expectNil: true,
},
{
Expand All @@ -222,7 +224,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol: "USD",
basePriceState: invalidPriceStateATOM,
quotePriceState: bandPriceStateUSD,
expectedPrice: nil,
expectedPrice: math.LegacyNewDec(-1),
expectNil: true,
},
{
Expand All @@ -231,7 +233,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol: "NOM",
basePriceState: bandPriceStateATOM,
quotePriceState: nil,
expectedPrice: nil, // Since NOM doesn't exist, expect nil
expectedPrice: math.LegacyNewDec(-1), // Since NOM doesn't exist, expect nil
expectNil: true,
},
// return a valid price
Expand All @@ -241,7 +243,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol: "NOM",
basePriceState: bandPriceStateATOM,
quotePriceState: bandPriceStateNOM,
expectedPrice: &expectedPrice05, // 10/2 = 5
expectedPrice: expectedPrice05, // 10/2 = 5
expectNil: false,
},
{
Expand All @@ -250,7 +252,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol: "USD",
basePriceState: bandPriceStateATOM,
quotePriceState: nil,
expectedPrice: &expectedPrice10, // Since quote = USD, we return base price directly
expectedPrice: expectedPrice10, // Since quote = USD, we return base price directly
expectNil: false,
},
{
Expand All @@ -259,7 +261,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol: "USD",
basePriceState: bandPriceStateATOM,
quotePriceState: bandPriceStateUSD,
expectedPrice: &expectedPrice10,
expectedPrice: expectedPrice10,
expectNil: false,
},
{
Expand All @@ -268,7 +270,7 @@ func TestGetPrice(t *testing.T) {
quoteSymbol: "ATOM",
basePriceState: bandPriceStateUSD,
quotePriceState: bandPriceStateATOM,
expectedPrice: &expectedPrice01,
expectedPrice: expectedPrice01,
expectNil: false,
},
}
Expand All @@ -286,19 +288,67 @@ func TestGetPrice(t *testing.T) {
}

// Execute GetPrice
price := app.OracleKeeper.GetPrice(ctx, tc.baseSymbol, tc.quoteSymbol)
price, err := app.OracleKeeper.GetPrice(ctx, tc.baseSymbol, tc.quoteSymbol)

// Check expectations
if tc.expectNil {
require.Nil(t, price)
require.Error(t, err)
} else {
require.NoError(t, err)
require.NotNil(t, price)
require.Equal(t, tc.expectedPrice, price)
}
})
}
}

func (s *KeeperTestSuite) TestPriceOld() {
s.SetupTest()
allowedPriceDelay := s.App.OracleKeeper.GetParams(s.Ctx).AllowedPriceDelay
var (
timeLate = -allowedPriceDelay - time.Hour // 6h +1h =7h
priceNOM = math.LegacyNewDec(2)
)

PricesState := []*types.BandPriceState{
{
Symbol: "ATOM",
Rate: math.NewInt(10),
ResolveTime: time.Now().Add(timeLate).Unix(), // old price
Request_ID: 1,
PriceState: *types.NewPriceState(math.LegacyNewDec(10), 1),
},
{
Symbol: "USD",
Rate: math.NewInt(1),
ResolveTime: time.Now().Unix(),
Request_ID: 1,
PriceState: *types.NewPriceState(math.LegacyNewDec(1), 1),
},
{
Symbol: "NOM",
Rate: math.NewInt(2),
ResolveTime: time.Now().Unix(),
Request_ID: 1,
PriceState: *types.NewPriceState(priceNOM, 1),
},
}

for _, priceState := range PricesState {
err := s.App.OracleKeeper.SetBandPriceState(s.Ctx, priceState.Symbol, priceState)
s.Require().NoError(err)
}

// ATOM price old
_, err := s.App.OracleKeeper.GetPrice(s.Ctx, "ATOM", "USD")
s.Require().Error(err)

// NOM price new (6h)
price, err := s.App.OracleKeeper.GetPrice(s.Ctx, "NOM", "USD")
s.Require().NoError(err)
s.Require().Equal(priceNOM, price)
}

func TestProcessBandOraclePrices(t *testing.T) {
// Set up the application and context
app := app.Setup(t, false)
Expand Down
5 changes: 4 additions & 1 deletion x/oracle/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (

apptesting "github.com/onomyprotocol/reserve/app/apptesting"
"github.com/onomyprotocol/reserve/x/oracle/keeper"
"github.com/onomyprotocol/reserve/x/oracle/types"
testifysuite "github.com/stretchr/testify/suite"
)

type KeeperTestSuite struct {
apptesting.KeeperTestHelper
k keeper.Keeper
k keeper.Keeper
msgServer types.MsgServer
}

func TestKeeperTestSuite(t *testing.T) {
Expand All @@ -20,4 +22,5 @@ func TestKeeperTestSuite(t *testing.T) {
func (s *KeeperTestSuite) SetupTest() {
s.Setup()
s.k = s.App.OracleKeeper
s.msgServer = keeper.NewMsgServerImpl(s.k)
}
47 changes: 25 additions & 22 deletions x/oracle/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
package keeper_test

// import (
// "context"
// "testing"

// "github.com/stretchr/testify/require"

// keepertest "github.com/onomyprotocol/reserve/testutil/keeper"
// "github.com/onomyprotocol/reserve/x/oracle/keeper"
// "github.com/onomyprotocol/reserve/x/oracle/types"
// )

// func setupMsgServer(t testing.TB) (keeper.Keeper, types.MsgServer, context.Context) {
// k, ctx := keepertest.OracleKeeper(t)
// return k, keeper.NewMsgServerImpl(k), ctx
// }

// func TestMsgServer(t *testing.T) {
// k, ms, ctx := setupMsgServer(t)
// require.NotNil(t, ms)
// require.NotNil(t, ctx)
// require.NotEmpty(t, k)
// }
import (
"time"

"github.com/onomyprotocol/reserve/x/oracle/types"
)

func (s *KeeperTestSuite) TestUpdateParams() {
s.SetupTest()

paramDefault := s.k.GetParams(s.Ctx)
s.Require().Equal(paramDefault.AllowedPriceDelay, types.DefauAllowedPriceDelay)

allowedPriceDelayUpdate := time.Hour * 10

msgUpdateParams := types.MsgUpdateParams{
Authority: s.k.GetAuthority(),
Params: types.NewParams(allowedPriceDelayUpdate),
}

_, err := s.msgServer.UpdateParams(s.Ctx, &msgUpdateParams)
s.Require().NoError(err)

paramsNew := s.k.GetParams(s.Ctx)
s.Require().Equal(paramsNew.AllowedPriceDelay, allowedPriceDelayUpdate)
}
7 changes: 3 additions & 4 deletions x/oracle/keeper/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"slices"
"strconv"

errors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/onomyprotocol/reserve/x/oracle/types"
)
Expand All @@ -25,9 +24,9 @@ func (k Keeper) BandPriceStates(c context.Context, _ *types.QueryBandPriceStates
func (k Keeper) Price(c context.Context, q *types.QueryPriceRequest) (*types.QueryPriceResponse, error) {
ctx := sdk.UnwrapSDKContext(c)

price := k.GetPrice(ctx, q.BaseDenom, q.QuoteDenom)
if price == nil || price.IsNil() {
return nil, errors.Wrapf(types.ErrInvalidOracle, "can not get price with base %s quote %s", q.BaseDenom, q.QuoteDenom)
price, err := k.GetPrice(ctx, q.BaseDenom, q.QuoteDenom)
if err != nil {
return nil, err
} else {
res := &types.QueryPriceResponse{
Price: price.String(),
Expand Down
Loading

0 comments on commit 3e24d9a

Please sign in to comment.