Skip to content

Commit

Permalink
[DFI-561] Oracle mod: Init/Export genesis (#179)
Browse files Browse the repository at this point in the history
* [DFI-561] Mod oracle: added import/export

* [DFI-561] Mod oracle: added genesis validation

* [DFI-561] Mod oracle: testing

* [DFI-561] Mod oracle: additional tests

* [DFI-561] Mod oracle: fixed imports

* [DFI-561] Mod oracle: fixed test and validation

* [DFI-561] Mod oracle: refactored addCurrentPrice method

* [DFI-561] Mod oracle: fixed env variable for integ test
  • Loading branch information
g3co authored Jul 31, 2020
1 parent 7ea9487 commit 211a9f0
Show file tree
Hide file tree
Showing 13 changed files with 456 additions and 67 deletions.
10 changes: 8 additions & 2 deletions app/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,14 @@ func CheckSetGenesisDVM(t *testing.T, app *DnServiceApp, accs []*auth.BaseAccoun

// read VM genesis from file and add to genesisState
{
vmGenesisPath := os.ExpandEnv("${GOPATH}/src/github.com/dfinance/dnode/x/vm/internal/keeper/genesis_ws.json")
stateBytes, err := ioutil.ReadFile(vmGenesisPath)
vmGenesisPath := "${GOPATH}/src/github.com/dfinance/dnode/x/vm/internal/keeper/genesis_ws.json"

vmGenesisPathEnv := os.Getenv("VMWSPATH")
if vmGenesisPathEnv != "" {
vmGenesisPath = vmGenesisPathEnv
}

stateBytes, err := ioutil.ReadFile(os.ExpandEnv(vmGenesisPath))
require.NoError(t, err, "reading VM genesis file")

genesisState[vm.ModuleName] = stateBytes
Expand Down
1 change: 1 addition & 0 deletions x/oracle/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type (
Oracle = types.Oracle
Oracles = types.Oracles
CurrentPrice = types.CurrentPrice
CurrentPrices = types.CurrentPrices
PostedPrice = types.PostedPrice
Keeper = keeper.Keeper
MsgAddOracle = types.MsgAddOracle
Expand Down
21 changes: 20 additions & 1 deletion x/oracle/internal/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"encoding/json"
"fmt"

"github.com/dfinance/dnode/x/oracle/internal/types"

Expand All @@ -14,15 +15,33 @@ func (k Keeper) InitGenesis(ctx sdk.Context, data json.RawMessage) {

state := types.GenesisState{}
k.cdc.MustUnmarshalJSON(data, &state)

if err := state.Validate(ctx.BlockTime()); err != nil {
panic(err)
}

k.SetParams(ctx, state.Params)

for _, cPrice := range state.CurrentPrices {
if _, ok := k.GetAsset(ctx, cPrice.AssetCode); !ok {
panic(fmt.Errorf("asset_code %s does not exist", cPrice.AssetCode))
}
k.addCurrentPrice(ctx, cPrice)
}
}

// ExportGenesis exports module genesis state using current params state.
func (k Keeper) ExportGenesis(ctx sdk.Context) json.RawMessage {
k.modulePerms.AutoCheck(types.PermRead)

currentPrices, err := k.GetCurrentPricesList(ctx)
if err != nil {
panic(err)
}

state := types.GenesisState{
Params: k.GetParams(ctx),
Params: k.GetParams(ctx),
CurrentPrices: currentPrices,
}

return k.cdc.MustMarshalJSON(state)
Expand Down
124 changes: 124 additions & 0 deletions x/oracle/internal/keeper/genesis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// +build unit

package keeper

import (
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

dnTypes "github.com/dfinance/dnode/helpers/types"
"github.com/dfinance/dnode/x/oracle/internal/types"
)

func TestOracleKeeper_Genesis_Init(t *testing.T) {
input := NewTestInput(t)

keeper := input.keeper
ctx := input.ctx
ctx = ctx.WithBlockTime(time.Now().Add(time.Hour))
ctx = ctx.WithBlockHeight(3)
cdc := input.cdc

// default genesis
{
keeper.InitGenesis(ctx, cdc.MustMarshalJSON(types.DefaultGenesisState()))
cpList, err := keeper.GetCurrentPricesList(input.ctx)
require.Nil(t, err)
params := keeper.GetParams(input.ctx)
require.Len(t, cpList, 0)
defaultParams := types.DefaultParams()
require.Equal(t, len(params.Assets), len(defaultParams.Assets))
}

// prices asset code doesnt exist in assets
{
params := input.keeper.GetParams(ctx)
params.Assets = types.Assets{
types.NewAsset(dnTypes.AssetCode("eth_dfi"), types.Oracles{}, true),
}

cpList := types.CurrentPrices{
NewMockCurrentPrice("btc_dfi", 100),
}

state := types.GenesisState{
Params: params,
CurrentPrices: cpList,
}

defer func() {
r := recover()
require.NotNil(t, r)
require.Contains(t, r.(error).Error(), "asset_code")
require.Contains(t, r.(error).Error(), "does not exist")
}()

keeper.InitGenesis(ctx, cdc.MustMarshalJSON(state))
}

//import and export
{
params := input.keeper.GetParams(ctx)

oracles := types.Oracles{
types.Oracle{
Address: sdk.AccAddress{},
},
}

params.Assets = types.Assets{
types.NewAsset(dnTypes.AssetCode("btc_dfi"), oracles, true),
types.NewAsset(dnTypes.AssetCode("eth_dfi"), oracles, true),
types.NewAsset(dnTypes.AssetCode("dfi_btc"), oracles, true),
types.NewAsset(dnTypes.AssetCode("usdt_dfi"), oracles, true),
}

cpList := types.CurrentPrices{
NewMockCurrentPrice("btc_dfi", 100),
NewMockCurrentPrice("eth_dfi", 200),
NewMockCurrentPrice("dfi_btc", 300),
NewMockCurrentPrice("usdt_dfi", 400),
}

state := types.GenesisState{
Params: params,
CurrentPrices: cpList,
}

// initialize and check current state with init values
keeper.InitGenesis(ctx, cdc.MustMarshalJSON(state))

cpListFromKeeper, err := keeper.GetCurrentPricesList(ctx)
require.Nil(t, err)
require.Len(t, cpListFromKeeper, len(cpList))

paramsFromKeeper := keeper.GetParams(ctx)
require.Equal(t, paramsFromKeeper.Assets, params.Assets)

// export and check exported values with initial
var exportedState types.GenesisState
cdc.MustUnmarshalJSON(keeper.ExportGenesis(ctx), &exportedState)

require.False(t, exportedState.IsEmpty())
require.Equal(t, exportedState.Params.Assets, params.Assets)
require.Equal(t, exportedState.Params.Nominees, params.Nominees)
require.Equal(t, exportedState.Params.PostPrice, params.PostPrice)
require.Equal(t, len(exportedState.CurrentPrices), len(state.CurrentPrices))

// checking all of items existing in the export
sumPrices := sdk.NewIntFromUint64(0)
for _, i := range exportedState.CurrentPrices {
sumPrices = sumPrices.Add(i.Price)
}

sumPricesInitial := sdk.NewIntFromUint64(0)
for _, i := range state.CurrentPrices {
sumPricesInitial = sumPricesInitial.Add(i.Price)
}

require.Equal(t, sumPrices, sumPricesInitial)
}
}
126 changes: 79 additions & 47 deletions x/oracle/internal/keeper/price.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"fmt"
"sort"
"time"

Expand All @@ -24,69 +25,42 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, assetCode dnTypes.AssetCode) ty
return price
}

// GetRawPrices fetches the set of all prices posted by oracles for an asset and specific blockHeight.
func (k Keeper) GetRawPrices(ctx sdk.Context, assetCode dnTypes.AssetCode, blockHeight int64) []types.PostedPrice {
// GetCurrentPricesList returns all current prices.
func (k Keeper) GetCurrentPricesList(ctx sdk.Context) (types.CurrentPrices, error) {
k.modulePerms.AutoCheck(types.PermRead)

store := ctx.KVStore(k.storeKey)
bz := store.Get(types.GetRawPricesKey(assetCode, blockHeight))
iterator := sdk.KVStorePrefixIterator(store, types.GetCurrentPricePrefix())
defer iterator.Close()

var prices []types.PostedPrice
k.cdc.MustUnmarshalBinaryBare(bz, &prices)
currentPrices := types.CurrentPrices{}

return prices
}
for ; iterator.Valid(); iterator.Next() {
cPrice := types.CurrentPrice{}
if err := k.cdc.UnmarshalBinaryBare(iterator.Value(), &cPrice); err != nil {
err = fmt.Errorf("order unmarshal: %w", err)
return nil, err
}
currentPrices = append(currentPrices, cPrice)
}

// SetPrice updates the posted price for a specific oracle.
func (k Keeper) SetPrice(
ctx sdk.Context,
oracle sdk.AccAddress,
assetCode dnTypes.AssetCode,
price sdk.Int,
receivedAt time.Time) (types.PostedPrice, error) {
return currentPrices, nil
}

// addCurrentPrice adds currentPrice item to the storage.
func (k Keeper) addCurrentPrice(ctx sdk.Context, currentPrice types.CurrentPrice) {
k.modulePerms.AutoCheck(types.PermWrite)

// validate price receivedAt timestamp comparing to the current blockHeight timestamp
if err := k.checkPriceReceivedAtTimestamp(ctx, receivedAt); err != nil {
return types.PostedPrice{}, err
}

// find raw price for specified oracle
store := ctx.KVStore(k.storeKey)
prices := k.GetRawPrices(ctx, assetCode, ctx.BlockHeight())
var index int
found := false
for i := range prices {
if prices[i].OracleAddress.Equals(oracle) {
index = i
found = true
break
}
}

// set the rawPrice for that particular oracle
if found {
prices[index] = types.PostedPrice{
AssetCode: assetCode, OracleAddress: oracle,
Price: price, ReceivedAt: receivedAt}
} else {
prices = append(prices, types.PostedPrice{
AssetCode: assetCode, OracleAddress: oracle,
Price: price, ReceivedAt: receivedAt})
index = len(prices) - 1
}

store.Set(types.GetRawPricesKey(assetCode, ctx.BlockHeight()), k.cdc.MustMarshalBinaryBare(prices))

return prices[index], nil
bz := k.cdc.MustMarshalBinaryBare(currentPrice)
store.Set(types.GetCurrentPriceKey(currentPrice.AssetCode), bz)
}

// SetCurrentPrices updates the price of an asset to the median of all valid oracle inputs and cleans up previous inputs.
func (k Keeper) SetCurrentPrices(ctx sdk.Context) error {
k.modulePerms.AutoCheck(types.PermWrite)

store := ctx.KVStore(k.storeKey)
assets := k.GetAssetParams(ctx)

updatesCnt := 0
Expand Down Expand Up @@ -144,7 +118,7 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context) error {
ReceivedAt: medianReceivedAt,
}

store.Set(types.GetCurrentPriceKey(assetCode), k.cdc.MustMarshalBinaryBare(newPrice))
k.addCurrentPrice(ctx, newPrice)

// save price to VM storage
priceVmAccessPath, priceVmValue := types.NewResPriceStorageValuesPanic(newPrice.AssetCode, newPrice.Price)
Expand All @@ -162,6 +136,64 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context) error {
return nil
}

// GetRawPrices fetches the set of all prices posted by oracles for an asset and specific blockHeight.
func (k Keeper) GetRawPrices(ctx sdk.Context, assetCode dnTypes.AssetCode, blockHeight int64) []types.PostedPrice {
k.modulePerms.AutoCheck(types.PermRead)

store := ctx.KVStore(k.storeKey)
bz := store.Get(types.GetRawPricesKey(assetCode, blockHeight))

var prices []types.PostedPrice
k.cdc.MustUnmarshalBinaryBare(bz, &prices)

return prices
}

// SetPrice updates the posted price for a specific oracle.
func (k Keeper) SetPrice(
ctx sdk.Context,
oracle sdk.AccAddress,
assetCode dnTypes.AssetCode,
price sdk.Int,
receivedAt time.Time) (types.PostedPrice, error) {

k.modulePerms.AutoCheck(types.PermWrite)

// validate price receivedAt timestamp comparing to the current blockHeight timestamp
if err := k.checkPriceReceivedAtTimestamp(ctx, receivedAt); err != nil {
return types.PostedPrice{}, err
}

// find raw price for specified oracle
store := ctx.KVStore(k.storeKey)
prices := k.GetRawPrices(ctx, assetCode, ctx.BlockHeight())
var index int
found := false
for i := range prices {
if prices[i].OracleAddress.Equals(oracle) {
index = i
found = true
break
}
}

// set the rawPrice for that particular oracle
if found {
prices[index] = types.PostedPrice{
AssetCode: assetCode, OracleAddress: oracle,
Price: price, ReceivedAt: receivedAt}
} else {
prices = append(prices, types.PostedPrice{
AssetCode: assetCode, OracleAddress: oracle,
Price: price, ReceivedAt: receivedAt})
index = len(prices) - 1
}

store.Set(types.GetRawPricesKey(assetCode, ctx.BlockHeight()), k.cdc.MustMarshalBinaryBare(prices))

return prices[index], nil
}

// nolint:errcheck
// ValidatePostPrice makes sure the person posting the price is an oracle.
func (k Keeper) ValidatePostPrice(ctx sdk.Context, msg types.MsgPostPrice) error {
Expand Down
Loading

0 comments on commit 211a9f0

Please sign in to comment.