From 9f6a5d08ed0125f64eb29041d0b94f3e3fd381d5 Mon Sep 17 00:00:00 2001 From: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:23:50 -0800 Subject: [PATCH] respect the pouch. Respect it --- proto/ojo/gmp/v1/tx.proto | 12 ++ x/gmp/keeper/keeper.go | 51 +++++-- x/gmp/types/abi_decode.go | 1 - x/gmp/types/abi_encode.go | 102 ++++++++++++- x/gmp/types/abi_encode_test.go | 4 +- x/gmp/types/abi_types.go | 1 - x/gmp/types/expected_keeper.go | 4 + x/gmp/types/gmp_messages.go | 65 -------- x/gmp/types/tx.pb.go | 241 +++++++++++++++++++++++++----- x/oracle/keeper/historic_price.go | 44 +++++- 10 files changed, 406 insertions(+), 119 deletions(-) delete mode 100644 x/gmp/types/gmp_messages.go diff --git a/proto/ojo/gmp/v1/tx.proto b/proto/ojo/gmp/v1/tx.proto index 96838445..47e57222 100644 --- a/proto/ojo/gmp/v1/tx.proto +++ b/proto/ojo/gmp/v1/tx.proto @@ -57,6 +57,18 @@ message MsgRelayPrice { (gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" ]; + + // contract_address defines the contract address to call. + string contract_address = 6; + + // command_selector defines the command to call. + bytes command_selector = 7; + + // command_params defines the command parameters to call. + bytes command_params = 8; + + // timestamp defines the timestamp of the message. + int64 timestamp = 9; } // MsgRelay defines the Relay response type. diff --git a/x/gmp/keeper/keeper.go b/x/gmp/keeper/keeper.go index e596ecec..b5f36a5c 100644 --- a/x/gmp/keeper/keeper.go +++ b/x/gmp/keeper/keeper.go @@ -7,6 +7,7 @@ import ( "time" "github.com/cometbft/cometbft/libs/log" + "github.com/ethereum/go-ethereum/common" sdk "github.com/cosmos/cosmos-sdk/types" ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" @@ -58,42 +59,66 @@ func (k Keeper) RelayPrice( ctx := sdk.UnwrapSDKContext(goCtx) params := k.GetParams(ctx) - // encode oracle data - rates := []types.PriceFeedData{} + prices := []types.PriceData{} for _, denom := range msg.Denoms { + // get exchange rate rate, err := k.oracleKeeper.GetExchangeRate(ctx, denom) if err != nil { return &types.MsgRelayPriceResponse{}, err } - priceFeed, err := types.NewPriceFeedData( + // get any available median and standard deviation data + medians := k.oracleKeeper.HistoricMedians( + ctx, + denom, + k.oracleKeeper.MaximumMedianStamps(ctx), + ) + deviations := k.oracleKeeper.HistoricDeviations( + ctx, + denom, + k.oracleKeeper.MaximumMedianStamps(ctx), + ) + // convert them to a medianData slice + medianData, err := types.NewMediansSlice(medians, deviations) + if err != nil { + return &types.MsgRelayPriceResponse{}, err + } + + priceFeed, err := types.NewPriceData( denom, rate, - // TODO: replace with actual resolve time & id - // Ref: https://github.com/ojo-network/ojo/issues/309 - big.NewInt(1), - big.NewInt(1), + big.NewInt(msg.Timestamp), + medianData, ) if err != nil { k.Logger(ctx).With(err).Error("unable to relay price to gmp") continue } - rates = append(rates, priceFeed) + prices = append(prices, priceFeed) } - // TODO: fill with actual disableResolve option - // Ref: https://github.com/ojo-network/ojo/issues/309 - /*payload, err := types.EncodeABI("postPrices", rates, false) + // convert commandSelector to [4]byte + var commandSelector [4]byte + copy(commandSelector[:], msg.CommandSelector) + + encoder := types.NewGMPEncoder( + prices, + msg.Denoms, + common.HexToAddress(msg.ContractAddress), + commandSelector, + msg.CommandParams, + ) + payload, err := encoder.GMPEncode() if err != nil { return nil, err - }*/ + } // package GMP message := types.GmpMessage{ DestinationChain: msg.DestinationChain, DestinationAddress: msg.DestinationAddress, - Payload: []byte{}, + Payload: payload, Type: types.TypeGeneralMessage, } bz, err := message.Marshal() diff --git a/x/gmp/types/abi_decode.go b/x/gmp/types/abi_decode.go index 8f68f3c8..f2a53010 100644 --- a/x/gmp/types/abi_decode.go +++ b/x/gmp/types/abi_decode.go @@ -17,7 +17,6 @@ type GmpDecoder struct { CommandSelector [4]byte CommandParams []byte Timestamp *big.Int - AbiEncodedData []byte } // abiSpec is the ABI specification for the GMP data. diff --git a/x/gmp/types/abi_encode.go b/x/gmp/types/abi_encode.go index 969e1596..0fc2242f 100644 --- a/x/gmp/types/abi_encode.go +++ b/x/gmp/types/abi_encode.go @@ -1,16 +1,30 @@ package types import ( + fmt "fmt" "math/big" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + oracletypes "github.com/ojo-network/ojo/x/oracle/types" +) + +const ( + // TypeUnrecognized means coin type is unrecognized + TypeUnrecognized = iota + // TypeGeneralMessage is a pure message + TypeGeneralMessage + // TypeGeneralMessageWithToken is a general message with token + TypeGeneralMessageWithToken + // TypeSendToken is a direct token transfer + TypeSendToken ) // GmpEncoder is the struct we use to encode the data we want to send to the GMP. type GmpEncoder struct { PriceData []PriceData - AssetNames [32]byte + AssetNames [][32]byte ContractAddress common.Address CommandSelector [4]byte CommandParams []byte @@ -37,7 +51,7 @@ var encoderSpec = abi.Arguments{ Type: priceDataType, }, { - Type: assetNameType, + Type: assetNamesType, }, { Type: contractAddressType, @@ -54,3 +68,87 @@ var encoderSpec = abi.Arguments{ func (g GmpEncoder) GMPEncode() ([]byte, error) { return encoderSpec.Pack(g.PriceData, g.AssetNames, g.ContractAddress, g.CommandSelector, g.CommandParams) } + +func NewGMPEncoder( + priceData []PriceData, + assetName []string, + contractAddress common.Address, + commandSelector [4]byte, + commandParams []byte, +) GmpEncoder { + return GmpEncoder{ + PriceData: priceData, + AssetNames: namesToBytes(assetName), + ContractAddress: contractAddress, + CommandSelector: commandSelector, + CommandParams: commandParams, + } +} + +func nameToBytes32(name string) [32]byte { + var nameBytes [32]byte + copy(nameBytes[:], []byte(name)) + return nameBytes +} + +func namesToBytes(assetNames []string) [][32]byte { + assetNamesBytes := make([][32]byte, len(assetNames)) + for i, name := range assetNames { + assetNamesBytes[i] = nameToBytes32(name) + } + return assetNamesBytes +} + +func NewPriceData( + assetName string, + price sdk.Dec, + resolveTime *big.Int, + medianData []MedianData, +) (PriceData, error) { + assetSlice := []byte(assetName) + if len(assetSlice) > 32 { + return PriceData{}, fmt.Errorf( + "asset name is too long to convert to array: %s", assetName, + ) + } + var assetArray [32]byte + copy(assetArray[:], assetSlice) + return PriceData{ + AssetName: assetArray, + Price: decToInt(price), + ResolveTime: resolveTime, + MedianData: medianData, + }, nil +} + +// DecToInt multiplies amount by rate factor to make it compatible with contracts. +func decToInt(amount sdk.Dec) *big.Int { + return amount.Mul(rateFactor).TruncateInt().BigInt() +} + +var rateFactor = sdk.NewDec(10).Power(9) + +// NewMediansSlice creates a slice of MedianData from slices of medians and deviations. +func NewMediansSlice(medians oracletypes.PriceStamps, deviations oracletypes.PriceStamps) ([]MedianData, error) { + if len(medians) != len(deviations) { + return nil, fmt.Errorf("length of medians and deviations must be equal") + } + // First, sort them so we'll be able to find matching blockNums. + sortedMedians := *medians.Sort() + sortedDeviations := *deviations.Sort() + + // Then, create the MedianData slice. + medianData := make([]MedianData, 0, len(medians)) + for i, median := range sortedMedians { + // If the median and deviation are not from the same block, skip. + if median.BlockNum != sortedDeviations[i].BlockNum { + continue + } + medianData = append(medianData, MedianData{ + BlockNums: []*big.Int{big.NewInt(int64(median.BlockNum))}, + Medians: []*big.Int{decToInt(median.ExchangeRate.Amount)}, + Deviations: []*big.Int{decToInt(sortedDeviations[i].ExchangeRate.Amount)}, + }) + } + return medianData, nil +} diff --git a/x/gmp/types/abi_encode_test.go b/x/gmp/types/abi_encode_test.go index a224a214..866cd32c 100644 --- a/x/gmp/types/abi_encode_test.go +++ b/x/gmp/types/abi_encode_test.go @@ -24,7 +24,7 @@ func TestEncode(t *testing.T) { }, }, }, - AssetNames: [32]byte{}, + AssetNames: [][32]byte{}, ContractAddress: common.Address{}, CommandSelector: [4]byte{}, CommandParams: []byte{}, @@ -35,7 +35,7 @@ func TestEncode(t *testing.T) { vals, err := encoderSpec.Unpack(bz) require.NoError(t, err) - require.Equal(t, message.AssetNames, vals[1].([32]byte)) + require.Equal(t, message.AssetNames, vals[1].([][32]byte)) require.Equal(t, message.ContractAddress, vals[2].(common.Address)) require.Equal(t, message.CommandSelector, vals[3].([4]byte)) require.Equal(t, message.CommandParams, vals[4].([]byte)) diff --git a/x/gmp/types/abi_types.go b/x/gmp/types/abi_types.go index edd84157..4a29704c 100644 --- a/x/gmp/types/abi_types.go +++ b/x/gmp/types/abi_types.go @@ -4,7 +4,6 @@ import "github.com/ethereum/go-ethereum/accounts/abi" // These are the types we use to encode and decode data to and from the GMP. var ( - assetNameType, _ = abi.NewType("bytes32", "bytes32", nil) assetNamesType, _ = abi.NewType("bytes32[]", "bytes32[]", nil) contractAddressType, _ = abi.NewType("address", "address", nil) commandSelectorType, _ = abi.NewType("bytes4", "bytes4", nil) diff --git a/x/gmp/types/expected_keeper.go b/x/gmp/types/expected_keeper.go index b87f80d2..fd9f387a 100644 --- a/x/gmp/types/expected_keeper.go +++ b/x/gmp/types/expected_keeper.go @@ -2,10 +2,14 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + oracletypes "github.com/ojo-network/ojo/x/oracle/types" ) // OracleKeeper defines the expected Oracle interface that is needed by the gmp module. type OracleKeeper interface { GetExchangeRate(ctx sdk.Context, symbol string) (sdk.Dec, error) GetExponent(ctx sdk.Context, denom string) (uint32, error) + MaximumMedianStamps(ctx sdk.Context) uint64 + HistoricMedians(ctx sdk.Context, denom string, numStamps uint64) oracletypes.PriceStamps + HistoricDeviations(ctx sdk.Context, denom string, numStamps uint64) oracletypes.PriceStamps } diff --git a/x/gmp/types/gmp_messages.go b/x/gmp/types/gmp_messages.go deleted file mode 100644 index fe206b64..00000000 --- a/x/gmp/types/gmp_messages.go +++ /dev/null @@ -1,65 +0,0 @@ -package types - -import ( - "fmt" - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -const ( - // TypeUnrecognized means coin type is unrecognized - TypeUnrecognized = iota - // TypeGeneralMessage is a pure message - TypeGeneralMessage - // TypeGeneralMessageWithToken is a general message with token - TypeGeneralMessageWithToken - // TypeSendToken is a direct token transfer - TypeSendToken -) - -var rateFactor = sdk.NewDec(10).Power(9) - -// PriceFeedData is a struct to represent the data that is relayed to other chains. -// It contains the asset name, value, resolve time, and id. -// The AssetName is an array of bytes, not a list, because lists are not -// compatible with ABI encoding. -// Note: the ID field here is declared as "Id" because of the ABI encoding. -type PriceFeedData struct { - AssetName [32]byte - Value *big.Int - ResolveTime *big.Int - //nolint:stylecheck - Id *big.Int -} - -// NewPriceFeedData creates a new PriceFeedData struct. -// It must convert the assetName string to a byte array. -// This array may not exceed 32 bytes. -// TODO: Add a test for this function. -func NewPriceFeedData( - assetName string, - value sdk.Dec, - resolveTime *big.Int, - id *big.Int, -) (PriceFeedData, error) { - assetSlice := []byte(assetName) - if len(assetSlice) > 32 { - return PriceFeedData{}, fmt.Errorf( - "asset name is too long to convert to array: %s", assetName, - ) - } - var assetArray [32]byte - copy(assetArray[:], assetSlice) - return PriceFeedData{ - AssetName: assetArray, - Value: decToInt(value), - ResolveTime: resolveTime, - Id: id, - }, nil -} - -// DecToInt multiplies amount by rate factor to make it compatible with contracts. -func decToInt(amount sdk.Dec) *big.Int { - return amount.Mul(rateFactor).TruncateInt().BigInt() -} diff --git a/x/gmp/types/tx.pb.go b/x/gmp/types/tx.pb.go index 0a76adcb..a10f7b62 100644 --- a/x/gmp/types/tx.pb.go +++ b/x/gmp/types/tx.pb.go @@ -109,7 +109,7 @@ func (m *MsgSetParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSetParamsResponse proto.InternalMessageInfo -// MsgRelayPrice defines the Relay message type. +// MsgRelay defines the Relay message type. type MsgRelayPrice struct { // authority is the address that signs the message. Relayer string `protobuf:"bytes,1,opt,name=relayer,proto3" json:"relayer,omitempty"` @@ -121,6 +121,14 @@ type MsgRelayPrice struct { Denoms []string `protobuf:"bytes,4,rep,name=denoms,proto3" json:"denoms,omitempty"` // token determines the IBC token that the user wants to relay via GMP. Token types.Coin `protobuf:"bytes,5,opt,name=token,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"token"` + // contract_address defines the contract address to call. + ContractAddress string `protobuf:"bytes,6,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty"` + // command_selector defines the command to call. + CommandSelector []byte `protobuf:"bytes,7,opt,name=command_selector,json=commandSelector,proto3" json:"command_selector,omitempty"` + // command_params defines the command parameters to call. + CommandParams []byte `protobuf:"bytes,8,opt,name=command_params,json=commandParams,proto3" json:"command_params,omitempty"` + // timestamp defines the timestamp of the message. + Timestamp int64 `protobuf:"varint,9,opt,name=timestamp,proto3" json:"timestamp,omitempty"` } func (m *MsgRelayPrice) Reset() { *m = MsgRelayPrice{} } @@ -203,39 +211,44 @@ func init() { func init() { proto.RegisterFile("ojo/gmp/v1/tx.proto", fileDescriptor_5f28b599a8e3f91d) } var fileDescriptor_5f28b599a8e3f91d = []byte{ - // 509 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x53, 0xb1, 0x6f, 0xd3, 0x4e, - 0x14, 0xb6, 0x9b, 0x5f, 0xf3, 0x53, 0x0e, 0x90, 0xe0, 0x1a, 0x52, 0x27, 0x83, 0x13, 0x32, 0xa0, - 0xa8, 0x28, 0x3e, 0x12, 0x24, 0x86, 0x4e, 0x90, 0x0e, 0x48, 0x48, 0x91, 0x2a, 0x77, 0x63, 0xa9, - 0x2e, 0xf6, 0xe9, 0xe2, 0x04, 0xdf, 0xb3, 0x7c, 0xd7, 0xd0, 0xac, 0x4c, 0x88, 0x89, 0x99, 0xa9, - 0x33, 0x13, 0x42, 0xfc, 0x11, 0x1d, 0x2b, 0x26, 0x26, 0x40, 0xc9, 0x00, 0x7f, 0x06, 0xb2, 0x7d, - 0x96, 0x0f, 0xa4, 0x8a, 0x29, 0xf7, 0xde, 0xf7, 0xbd, 0xef, 0xde, 0x7d, 0x5f, 0x8c, 0xf6, 0x60, - 0x01, 0x84, 0xc7, 0x09, 0x59, 0x8d, 0x88, 0x3a, 0xf7, 0x92, 0x14, 0x14, 0x60, 0x04, 0x0b, 0xf0, - 0x78, 0x9c, 0x78, 0xab, 0x51, 0xa7, 0xc9, 0x81, 0x43, 0xde, 0x26, 0xd9, 0xa9, 0x60, 0x74, 0x9a, - 0xc6, 0x58, 0x46, 0x2c, 0xba, 0xed, 0x00, 0x64, 0x0c, 0xf2, 0xb4, 0xa0, 0x17, 0x85, 0x86, 0xf6, - 0x8b, 0x8a, 0xc4, 0x92, 0x67, 0x33, 0xb1, 0xe4, 0x1a, 0x70, 0x35, 0x30, 0xa3, 0x92, 0x91, 0xd5, - 0x68, 0xc6, 0x14, 0x1d, 0x91, 0x00, 0x22, 0x51, 0xe0, 0xfd, 0xb7, 0x36, 0xba, 0x39, 0x95, 0xfc, - 0x84, 0xa9, 0x63, 0x9a, 0xd2, 0x58, 0xe2, 0xc7, 0xa8, 0x41, 0xcf, 0xd4, 0x1c, 0xd2, 0x48, 0xad, - 0x1d, 0xbb, 0x67, 0x0f, 0x1a, 0x13, 0xe7, 0xcb, 0xe7, 0x61, 0x53, 0x5f, 0xf7, 0x34, 0x0c, 0x53, - 0x26, 0xe5, 0x89, 0x4a, 0x23, 0xc1, 0xfd, 0x8a, 0x8a, 0x0f, 0x50, 0x3d, 0xc9, 0x15, 0x9c, 0x9d, - 0x9e, 0x3d, 0xb8, 0x31, 0xc6, 0x5e, 0xf5, 0x4a, 0xaf, 0xd0, 0xf6, 0x35, 0xe3, 0xb0, 0xf5, 0xe6, - 0xa2, 0x6b, 0xfd, 0xba, 0xe8, 0x5a, 0xaf, 0x7f, 0x7e, 0x3c, 0xa8, 0x34, 0xfa, 0x2d, 0xd4, 0x34, - 0x77, 0xf1, 0x99, 0x4c, 0x40, 0x48, 0xd6, 0xff, 0xb4, 0x83, 0x6e, 0x4d, 0x25, 0xf7, 0xd9, 0x4b, - 0xba, 0x3e, 0x4e, 0xa3, 0x80, 0xe1, 0x31, 0xfa, 0x3f, 0xcd, 0x2a, 0x96, 0xfe, 0x73, 0xc7, 0x92, - 0x88, 0x1f, 0xa0, 0x3b, 0x21, 0x93, 0x2a, 0x12, 0x54, 0x45, 0x20, 0x4e, 0x83, 0x39, 0x8d, 0x44, - 0xbe, 0x6c, 0xc3, 0xbf, 0x6d, 0x00, 0x47, 0x59, 0x1f, 0x13, 0xb4, 0x67, 0x92, 0x69, 0x21, 0xe9, - 0xd4, 0x72, 0x3a, 0x36, 0x20, 0x7d, 0x19, 0x6e, 0xa1, 0x7a, 0xc8, 0x04, 0xc4, 0xd2, 0xf9, 0xaf, - 0x57, 0x1b, 0x34, 0x7c, 0x5d, 0x61, 0x8a, 0x76, 0x15, 0x2c, 0x99, 0x70, 0x76, 0x73, 0x5b, 0xda, - 0x9e, 0x5e, 0x32, 0x0b, 0xc4, 0xd3, 0x81, 0x78, 0x47, 0x10, 0x89, 0xc9, 0xc3, 0xcb, 0x6f, 0x5d, - 0xeb, 0xc3, 0xf7, 0xee, 0x80, 0x47, 0x6a, 0x7e, 0x36, 0xf3, 0x02, 0x88, 0x75, 0xc8, 0xfa, 0x67, - 0x28, 0xc3, 0x25, 0x51, 0xeb, 0x84, 0xc9, 0x7c, 0x40, 0xfa, 0x85, 0xf2, 0x61, 0xd3, 0xb4, 0xb3, - 0x7c, 0x6e, 0x7f, 0x1f, 0xdd, 0xfd, 0xc3, 0xb3, 0xd2, 0xcd, 0xf1, 0x7b, 0x1b, 0xd5, 0xa6, 0x92, - 0xe3, 0x67, 0xa8, 0x51, 0xc5, 0xee, 0x98, 0x71, 0x99, 0x21, 0x74, 0x7a, 0xd7, 0x21, 0xa5, 0x20, - 0x7e, 0x8e, 0x90, 0x11, 0x4d, 0xfb, 0x2f, 0x7e, 0x05, 0x75, 0xee, 0x5d, 0x0b, 0x95, 0x5a, 0x93, - 0x27, 0x97, 0x1b, 0xd7, 0xbe, 0xda, 0xb8, 0xf6, 0x8f, 0x8d, 0x6b, 0xbf, 0xdb, 0xba, 0xd6, 0xd5, - 0xd6, 0xb5, 0xbe, 0x6e, 0x5d, 0xeb, 0xc5, 0x7d, 0xc3, 0x16, 0x58, 0xc0, 0x50, 0x30, 0xf5, 0x0a, - 0xd2, 0x65, 0x76, 0x26, 0xe7, 0xf9, 0xc7, 0x92, 0x5b, 0x33, 0xab, 0xe7, 0x7f, 0xec, 0x47, 0xbf, - 0x03, 0x00, 0x00, 0xff, 0xff, 0x63, 0x69, 0x51, 0x3b, 0x7b, 0x03, 0x00, 0x00, + // 580 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x53, 0x31, 0x6f, 0xd3, 0x40, + 0x18, 0xb5, 0x09, 0x4d, 0xf1, 0xd1, 0x42, 0xb9, 0x86, 0xd6, 0x8d, 0x90, 0x63, 0x22, 0x81, 0x4c, + 0x51, 0x6c, 0x12, 0x24, 0x86, 0x4e, 0x90, 0x0e, 0x48, 0x48, 0x91, 0x2a, 0x67, 0x63, 0x89, 0x2e, + 0xf6, 0xc9, 0x71, 0xd2, 0xbb, 0xb3, 0x7c, 0xd7, 0xd0, 0xac, 0x4c, 0x88, 0x89, 0x99, 0xa9, 0x33, + 0x13, 0x03, 0x3f, 0xa2, 0x0b, 0x52, 0xc5, 0xc4, 0x04, 0x28, 0x19, 0xe0, 0x67, 0x20, 0x9f, 0xcf, + 0xd8, 0x20, 0x55, 0x4c, 0xf1, 0xf7, 0xde, 0xbb, 0xe7, 0x17, 0xbf, 0xef, 0xc0, 0x36, 0x9b, 0x32, + 0x2f, 0x22, 0x89, 0x37, 0xef, 0x7a, 0xe2, 0xd4, 0x4d, 0x52, 0x26, 0x18, 0x04, 0x6c, 0xca, 0xdc, + 0x88, 0x24, 0xee, 0xbc, 0xdb, 0x6c, 0x44, 0x2c, 0x62, 0x12, 0xf6, 0xb2, 0xa7, 0x5c, 0xd1, 0x6c, + 0x54, 0x8e, 0x65, 0xc2, 0x1c, 0xdd, 0x0b, 0x18, 0x27, 0x8c, 0x8f, 0x72, 0x79, 0x3e, 0x28, 0x6a, + 0x37, 0x9f, 0x3c, 0xc2, 0xa3, 0xec, 0x0c, 0xe1, 0x91, 0x22, 0x2c, 0x45, 0x8c, 0x11, 0xc7, 0xde, + 0xbc, 0x3b, 0xc6, 0x02, 0x75, 0xbd, 0x80, 0xc5, 0x34, 0xe7, 0xdb, 0x6f, 0x75, 0xb0, 0x31, 0xe0, + 0xd1, 0x10, 0x8b, 0x23, 0x94, 0x22, 0xc2, 0xe1, 0x13, 0x60, 0xa0, 0x13, 0x31, 0x61, 0x69, 0x2c, + 0x16, 0xa6, 0x6e, 0xeb, 0x8e, 0xd1, 0x37, 0xbf, 0x7c, 0xea, 0x34, 0xd4, 0xeb, 0x9e, 0x85, 0x61, + 0x8a, 0x39, 0x1f, 0x8a, 0x34, 0xa6, 0x91, 0x5f, 0x4a, 0xe1, 0x3e, 0xa8, 0x27, 0xd2, 0xc1, 0xbc, + 0x62, 0xeb, 0xce, 0xf5, 0x1e, 0x74, 0xcb, 0x7f, 0xe9, 0xe6, 0xde, 0xbe, 0x52, 0x1c, 0xec, 0xbc, + 0x39, 0x6b, 0x69, 0xbf, 0xce, 0x5a, 0xda, 0xeb, 0x9f, 0x1f, 0xf7, 0x4b, 0x8f, 0xf6, 0x0e, 0x68, + 0x54, 0xb3, 0xf8, 0x98, 0x27, 0x8c, 0x72, 0xdc, 0xfe, 0x5c, 0x03, 0x9b, 0x03, 0x1e, 0xf9, 0xf8, + 0x18, 0x2d, 0x8e, 0xd2, 0x38, 0xc0, 0xb0, 0x07, 0xd6, 0xd3, 0x6c, 0xc2, 0xe9, 0x7f, 0x33, 0x16, + 0x42, 0xf8, 0x10, 0xdc, 0x0a, 0x31, 0x17, 0x31, 0x45, 0x22, 0x66, 0x74, 0x14, 0x4c, 0x50, 0x4c, + 0x65, 0x58, 0xc3, 0xdf, 0xaa, 0x10, 0x87, 0x19, 0x0e, 0x3d, 0xb0, 0x5d, 0x15, 0xa3, 0xdc, 0xd2, + 0xac, 0x49, 0x39, 0xac, 0x50, 0xea, 0x65, 0x70, 0x07, 0xd4, 0x43, 0x4c, 0x19, 0xe1, 0xe6, 0x55, + 0xbb, 0xe6, 0x18, 0xbe, 0x9a, 0x20, 0x02, 0x6b, 0x82, 0xcd, 0x30, 0x35, 0xd7, 0xe4, 0x67, 0xd9, + 0x73, 0x55, 0xc8, 0xac, 0x10, 0x57, 0x15, 0xe2, 0x1e, 0xb2, 0x98, 0xf6, 0x1f, 0x9d, 0x7f, 0x6b, + 0x69, 0x1f, 0xbe, 0xb7, 0x9c, 0x28, 0x16, 0x93, 0x93, 0xb1, 0x1b, 0x30, 0xa2, 0x4a, 0x56, 0x3f, + 0x1d, 0x1e, 0xce, 0x3c, 0xb1, 0x48, 0x30, 0x97, 0x07, 0xb8, 0x9f, 0x3b, 0xc3, 0x07, 0x60, 0x2b, + 0x60, 0x54, 0xa4, 0x28, 0x10, 0x7f, 0x82, 0xd6, 0x65, 0xd0, 0x9b, 0x05, 0x5e, 0xa4, 0x94, 0x52, + 0x42, 0x10, 0x0d, 0x47, 0x1c, 0x1f, 0xe3, 0x40, 0xb0, 0xd4, 0x5c, 0xb7, 0x75, 0x67, 0x23, 0x93, + 0x4a, 0x7c, 0xa8, 0x60, 0x78, 0x0f, 0xdc, 0x28, 0xa4, 0xaa, 0xd8, 0x6b, 0x52, 0xb8, 0xa9, 0x50, + 0xb5, 0x2f, 0x77, 0x80, 0x21, 0x62, 0x82, 0xb9, 0x40, 0x24, 0x31, 0x0d, 0x5b, 0x77, 0x6a, 0x7e, + 0x09, 0x1c, 0x34, 0xaa, 0x4d, 0x17, 0x4d, 0xb4, 0x77, 0xc1, 0xed, 0xbf, 0xea, 0x2c, 0x8a, 0xee, + 0xbd, 0xd7, 0x41, 0x6d, 0xc0, 0x23, 0xf8, 0x1c, 0x18, 0xe5, 0x46, 0x9a, 0xd5, 0x4d, 0xaa, 0xee, + 0x47, 0xd3, 0xbe, 0x8c, 0x29, 0x0c, 0xe1, 0x0b, 0x00, 0x2a, 0x5b, 0xb3, 0xf7, 0x8f, 0xbe, 0xa4, + 0x9a, 0x77, 0x2f, 0xa5, 0x0a, 0xaf, 0xfe, 0xd3, 0xf3, 0xa5, 0xa5, 0x5f, 0x2c, 0x2d, 0xfd, 0xc7, + 0xd2, 0xd2, 0xdf, 0xad, 0x2c, 0xed, 0x62, 0x65, 0x69, 0x5f, 0x57, 0x96, 0xf6, 0xf2, 0x7e, 0xa5, + 0x31, 0x36, 0x65, 0x1d, 0x8a, 0xc5, 0x2b, 0x96, 0xce, 0xb2, 0x67, 0xef, 0x54, 0xde, 0x63, 0xd9, + 0xda, 0xb8, 0x2e, 0xef, 0xdc, 0xe3, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x26, 0xc3, 0xe0, 0xcf, + 0x16, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -443,6 +456,32 @@ func (m *MsgRelayPrice) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Timestamp != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x48 + } + if len(m.CommandParams) > 0 { + i -= len(m.CommandParams) + copy(dAtA[i:], m.CommandParams) + i = encodeVarintTx(dAtA, i, uint64(len(m.CommandParams))) + i-- + dAtA[i] = 0x42 + } + if len(m.CommandSelector) > 0 { + i -= len(m.CommandSelector) + copy(dAtA[i:], m.CommandSelector) + i = encodeVarintTx(dAtA, i, uint64(len(m.CommandSelector))) + i-- + dAtA[i] = 0x3a + } + if len(m.ContractAddress) > 0 { + i -= len(m.ContractAddress) + copy(dAtA[i:], m.ContractAddress) + i = encodeVarintTx(dAtA, i, uint64(len(m.ContractAddress))) + i-- + dAtA[i] = 0x32 + } { size, err := m.Token.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -572,6 +611,21 @@ func (m *MsgRelayPrice) Size() (n int) { } l = m.Token.Size() n += 1 + l + sovTx(uint64(l)) + l = len(m.ContractAddress) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.CommandSelector) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.CommandParams) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovTx(uint64(m.Timestamp)) + } return n } @@ -948,6 +1002,125 @@ func (m *MsgRelayPrice) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContractAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContractAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CommandSelector", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CommandSelector = append(m.CommandSelector[:0], dAtA[iNdEx:postIndex]...) + if m.CommandSelector == nil { + m.CommandSelector = []byte{} + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CommandParams", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CommandParams = append(m.CommandParams[:0], dAtA[iNdEx:postIndex]...) + if m.CommandParams == nil { + m.CommandParams = []byte{} + } + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/x/oracle/keeper/historic_price.go b/x/oracle/keeper/historic_price.go index 628f4a73..19192388 100644 --- a/x/oracle/keeper/historic_price.go +++ b/x/oracle/keeper/historic_price.go @@ -26,6 +26,21 @@ func (k Keeper) HistoricMedians( return medians } +func (k Keeper) HistoricDeviations( + ctx sdk.Context, + denom string, + numStamps uint64, +) types.PriceStamps { + deviations := types.PriceStamps{} + + k.IterateHistoricDeviations(ctx, denom, uint(numStamps), func(median types.PriceStamp) bool { + deviations = append(deviations, median) + return false + }) + + return deviations +} + // CalcAndSetHistoricMedian uses all the historic prices of a given denom to // calculate its median price at the current block and set it to the store. // It will also call setMedianDeviation with the calculated median. @@ -256,7 +271,7 @@ func (k Keeper) IterateHistoricPrices( } } -// IterateHistoricMediansSinceBlock iterates over medians of a given +// IterateHistoricMedians iterates over medians of a given // denom in the store in reverse. // Iterator stops when exhausting the source, or when the handler returns `true`. func (k Keeper) IterateHistoricMedians( @@ -283,6 +298,33 @@ func (k Keeper) IterateHistoricMedians( } } +// IterateHistoricDeviations iterates over medians of a given +// denom in the store in reverse. +// Iterator stops when exhausting the source, or when the handler returns `true`. +func (k Keeper) IterateHistoricDeviations( + ctx sdk.Context, + denom string, + numStamps uint, + handler func(types.PriceStamp) bool, +) { + store := ctx.KVStore(k.storeKey) + + // make sure we have one zero byte to correctly separate denoms + prefix := util.ConcatBytes(1, types.KeyPrefixMedianDeviation, []byte(denom)) + iter := sdk.KVStoreReversePrefixIteratorPaginated(store, prefix, 1, numStamps) + defer iter.Close() + + for ; iter.Valid(); iter.Next() { + denom, block := types.ParseDenomAndBlockFromKey(iter.Key(), types.KeyPrefixMedian) + decProto := sdk.DecProto{} + k.cdc.MustUnmarshal(iter.Value(), &decProto) + price := types.NewPriceStamp(decProto.Dec, denom, block) + if handler(*price) { + break + } + } +} + // AddHistoricPrice adds the historic price of a denom at the current // block height. func (k Keeper) AddHistoricPrice(