From 90b37cabd60622ea9915d8b5b957752ab5e8de03 Mon Sep 17 00:00:00 2001 From: Valeriy Kabisov Date: Mon, 5 Oct 2020 20:16:30 +0900 Subject: [PATCH] [DFI-794] reversed asset price (#208) * [DFI-794] reverted asset price * [DFI-794] change to sdk.Int prices from oracle * [DFI-794] added reverse asset tests * [DFI-794] fixed querier * [DFI-794] typo fix --- app/cli_test.go | 38 +++-- app/rest_test.go | 16 +- app/unit_oracle_test.go | 60 +++++--- go.mod | 1 + go.sum | 2 + helpers/input_parsers.go | 10 ++ helpers/tests/clitester/cli_tester_queries.go | 4 +- helpers/tests/clitester/cli_tester_rest.go | 8 +- helpers/tests/clitester/cli_tester_txs.go | 5 +- helpers/types/asset_code.go | 15 ++ x/oracle/alias.go | 6 +- x/oracle/client/cli/queries.go | 2 +- x/oracle/client/cli/tx.go | 20 ++- x/oracle/client/rest/rest.go | 16 +- x/oracle/handler.go | 2 +- x/oracle/internal/keeper/genesis_test.go | 23 +-- x/oracle/internal/keeper/price.go | 67 +++++--- x/oracle/internal/keeper/price_test.go | 45 ++++-- x/oracle/internal/keeper/querier.go | 26 +++- x/oracle/internal/keeper/querier_test.go | 6 + x/oracle/internal/types/events.go | 6 +- x/oracle/internal/types/genesis_test.go | 8 +- x/oracle/internal/types/msg_post_price.go | 26 ++-- .../internal/types/msg_post_price_test.go | 22 +-- x/oracle/internal/types/price.go | 63 ++++++-- x/oracle/internal/types/price_test.go | 31 ++-- x/vm/internal/keeper/integ_test.go | 143 +++++++++++++----- 27 files changed, 483 insertions(+), 188 deletions(-) diff --git a/app/cli_test.go b/app/cli_test.go index ae4694c6..7afdd057 100644 --- a/app/cli_test.go +++ b/app/cli_test.go @@ -426,26 +426,29 @@ func TestOracle_CLI(t *testing.T) { postPrices := []struct { assetCode dnTypes.AssetCode sender string - price sdk.Int + askPrice sdk.Int + bidPrice sdk.Int receivedAt time.Time }{ { assetCode: assetCode, sender: assetOracle1, - price: sdk.NewInt(100), + askPrice: sdk.NewInt(100), + bidPrice: sdk.NewInt(95), receivedAt: now, }, { assetCode: assetCode, sender: assetOracle2, - price: sdk.NewInt(150), + askPrice: sdk.NewInt(150), + bidPrice: sdk.NewInt(149), receivedAt: now.Add(1 * time.Second), }, } startBlockHeight := ct.WaitForNextBlocks(1) for _, postPrice := range postPrices { - tx := ct.TxOraclePostPrice(postPrice.sender, postPrice.assetCode, postPrice.price, postPrice.receivedAt) + tx := ct.TxOraclePostPrice(postPrice.sender, postPrice.assetCode, postPrice.askPrice, postPrice.bidPrice, postPrice.receivedAt) tx.CheckSucceeded() } endBlockHeight := ct.WaitForNextBlocks(1) @@ -464,7 +467,8 @@ func TestOracle_CLI(t *testing.T) { rawPrice := rawPricesRange[i] require.Equal(t, postPrice.assetCode, rawPrice.AssetCode) require.Equal(t, postPrice.sender, rawPrice.OracleAddress.String()) - require.True(t, postPrice.price.Equal(rawPrice.Price)) + require.True(t, postPrice.askPrice.Equal(rawPrice.AskPrice)) + require.True(t, postPrice.bidPrice.Equal(rawPrice.BidPrice)) require.True(t, postPrice.receivedAt.Equal(rawPrice.ReceivedAt)) } @@ -472,26 +476,28 @@ func TestOracle_CLI(t *testing.T) { { // invalid number of args { - tx := ct.TxOraclePostPrice(assetOracle1, assetCode, sdk.OneInt(), time.Now()) + tx := ct.TxOraclePostPrice(assetOracle1, assetCode, sdk.NewInt(3), sdk.NewInt(2), time.Now()) tx.RemoveCmdArg(assetCode.String()) tx.CheckFailedWithErrorSubstring("arg(s)") } // invalid price { - tx := ct.TxOraclePostPrice(assetOracle1, assetCode, sdk.OneInt(), time.Now()) - tx.ChangeCmdArg(sdk.OneInt().String(), "not_int") - tx.CheckFailedWithErrorSubstring("parsing Int") + ask := sdk.NewInt(3) + tx := ct.TxOraclePostPrice(assetOracle1, assetCode, ask, sdk.NewInt(2), time.Now()) + tx.ChangeCmdArg(ask.String(), "not_int") + tx.CheckFailedWithErrorSubstring("parsing Int:") } // invalid receivedAt { now := time.Now() - tx := ct.TxOraclePostPrice(assetOracle1, assetCode, sdk.OneInt(), now) + tx := ct.TxOraclePostPrice(assetOracle1, assetCode, sdk.NewInt(3), sdk.NewInt(2), now) tx.ChangeCmdArg(strconv.FormatInt(now.Unix(), 10), "not_time.Time") tx.CheckFailedWithErrorSubstring("parsing Int") } + // MsgPostPrice ValidateBasic { - tx := ct.TxOraclePostPrice(assetOracle1, assetCode, sdk.NewIntWithDecimal(1, 20), time.Now()) + tx := ct.TxOraclePostPrice(assetOracle1, assetCode, sdk.NewIntWithDecimal(1, 20), sdk.NewIntWithDecimal(1, 20), time.Now()) tx.CheckFailedWithErrorSubstring("bytes limit") } } @@ -590,6 +596,16 @@ func TestOracle_CLI(t *testing.T) { require.Equal(t, assetCode, price.AssetCode) require.False(t, price.Price.IsZero()) + // query reversed asset + { + rvAsset := assetCode.ReverseCode() + q, price := ct.QueryOraclePrice(rvAsset) + q.CheckSucceeded() + + require.Equal(t, rvAsset, price.AssetCode) + require.False(t, price.Price.IsZero()) + } + // check incorrect inputs { // invalid number of args diff --git a/app/rest_test.go b/app/rest_test.go index 546c8328..c850618d 100644 --- a/app/rest_test.go +++ b/app/rest_test.go @@ -601,7 +601,8 @@ func TestOracle_REST(t *testing.T) { SenderIdx uint OracleName string OracleAddress string - Price sdk.Int + AskPrice sdk.Int + BidPrice sdk.Int ReceivedAt time.Time BlockHeight int64 }{ @@ -610,7 +611,8 @@ func TestOracle_REST(t *testing.T) { SenderIdx: 0, OracleName: oracleName1, OracleAddress: oracleAddr1, - Price: sdk.NewInt(100), + AskPrice: sdk.NewInt(100), + BidPrice: sdk.NewInt(99), ReceivedAt: now, BlockHeight: 0, }, @@ -619,7 +621,8 @@ func TestOracle_REST(t *testing.T) { SenderIdx: 1, OracleName: oracleName2, OracleAddress: oracleAddr2, - Price: sdk.NewInt(200), + AskPrice: sdk.NewInt(200), + BidPrice: sdk.NewInt(199), ReceivedAt: now.Add(5 * time.Second), }, } @@ -630,7 +633,7 @@ func TestOracle_REST(t *testing.T) { // it's not easy to find out when those TXs are Delivered prevBlockHeight := ct.WaitForNextBlocks(1) for _, postPrice := range postPrices { - req, _ := ct.RestTxOraclePostPrice(postPrice.OracleName, postPrice.AssetCode, postPrice.Price, postPrice.ReceivedAt) + req, _ := ct.RestTxOraclePostPrice(postPrice.OracleName, postPrice.AssetCode, postPrice.AskPrice, postPrice.BidPrice, postPrice.ReceivedAt) req.CheckSucceeded() } curBlockHeight := ct.WaitForNextBlocks(1) @@ -651,7 +654,8 @@ func TestOracle_REST(t *testing.T) { postPrice := postPrices[i] require.Equal(t, rawPrice.AssetCode, postPrice.AssetCode) require.Equal(t, postPrice.OracleAddress, rawPrice.OracleAddress.String()) - require.True(t, rawPrice.Price.Equal(postPrice.Price)) + require.True(t, rawPrice.AskPrice.Equal(postPrice.AskPrice)) + require.True(t, rawPrice.BidPrice.Equal(postPrice.BidPrice)) require.True(t, rawPrice.ReceivedAt.Equal(postPrice.ReceivedAt)) } } @@ -685,7 +689,7 @@ func TestOracle_REST(t *testing.T) { req, respMsg := ct.RestQueryOraclePrice(ct.DefAssetCode) req.CheckSucceeded() - require.True(t, respMsg.Price.Equal(postPrices[1].Price)) + require.True(t, respMsg.Price.Equal(postPrices[1].AskPrice)) require.True(t, respMsg.ReceivedAt.Equal(postPrices[1].ReceivedAt)) // check invalid inputs diff --git a/app/unit_oracle_test.go b/app/unit_oracle_test.go index 738a3e42..0734a9ac 100644 --- a/app/unit_oracle_test.go +++ b/app/unit_oracle_test.go @@ -76,7 +76,7 @@ func TestOracle_Queries(t *testing.T) { // getCurrentPrice query check (no inputs yet) { - response := oracle.CurrentPrice{} + response := oracle.CurrentAssetPrice{} CheckRunQuery(t, app, nil, fmt.Sprintf(queryOracleGetCurrentPricePathFmt, assetCode), &response) require.Empty(t, response.AssetCode) require.True(t, response.Price.IsZero()) @@ -84,7 +84,8 @@ func TestOracle_Queries(t *testing.T) { } now := time.Now() - priceValues := []sdk.Int{sdk.NewInt(1000), sdk.NewInt(2000), sdk.NewInt(1500)} + priceAskValues := []sdk.Int{sdk.NewInt(1001), sdk.NewInt(2002), sdk.NewInt(1501)} + priceBidValues := []sdk.Int{sdk.NewInt(1000), sdk.NewInt(2000), sdk.NewInt(1500)} priceTimestamps := []time.Time{now.Add(1 * time.Second), now.Add(2 * time.Second), now.Add(3 * time.Second)} // post prices @@ -97,7 +98,8 @@ func TestOracle_Queries(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceValues[0], + AskPrice: priceAskValues[0], + BidPrice: priceBidValues[0], ReceivedAt: priceTimestamps[0], } @@ -111,7 +113,8 @@ func TestOracle_Queries(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceValues[1], + AskPrice: priceAskValues[1], + BidPrice: priceBidValues[1], ReceivedAt: priceTimestamps[1], } @@ -125,7 +128,8 @@ func TestOracle_Queries(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceValues[2], + AskPrice: priceAskValues[2], + BidPrice: priceBidValues[2], ReceivedAt: priceTimestamps[2], } @@ -151,7 +155,8 @@ func TestOracle_Queries(t *testing.T) { CheckRunQuery(t, app, nil, fmt.Sprintf(queryOracleGetRawPricesPathFmt, assetCode, GetContext(app, true).BlockHeight()-1), &response) require.Len(t, response, 3) for i, rawPrice := range response { - require.True(t, priceValues[i].Equal(rawPrice.Price)) + require.True(t, priceAskValues[i].Equal(rawPrice.AskPrice)) + require.True(t, priceBidValues[i].Equal(rawPrice.BidPrice)) require.True(t, priceTimestamps[i].Equal(rawPrice.ReceivedAt)) require.Equal(t, assetCode, rawPrice.AssetCode) require.Equal(t, genAddrs[i], rawPrice.OracleAddress) @@ -160,10 +165,10 @@ func TestOracle_Queries(t *testing.T) { // getCurrentPrice query check (value should be calculated after BlockEnd) { - response := oracle.CurrentPrice{} + response := oracle.CurrentAssetPrice{} CheckRunQuery(t, app, nil, fmt.Sprintf(queryOracleGetCurrentPricePathFmt, assetCode), &response) require.Equal(t, assetCode, response.AssetCode) - require.True(t, response.Price.Equal(priceValues[2])) + require.True(t, response.Price.Equal(priceAskValues[2])) require.True(t, response.ReceivedAt.Equal(priceTimestamps[2])) } } @@ -518,7 +523,8 @@ func TestOracle_PostPrices(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: sdk.OneInt(), + AskPrice: sdk.OneInt(), + BidPrice: sdk.OneInt(), ReceivedAt: time.Now(), } @@ -534,7 +540,8 @@ func TestOracle_PostPrices(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: "non-existing-asset", - Price: sdk.OneInt(), + AskPrice: sdk.OneInt(), + BidPrice: sdk.OneInt(), ReceivedAt: time.Now(), } @@ -560,7 +567,8 @@ func TestOracle_PostPrices(t *testing.T) { // check posting price few times from the same oracle { now := time.Now() - priceAmount1, priceAmount2 := sdk.NewInt(200000000), sdk.NewInt(100000000) + priceAskAmount1, priceAskAmount2 := sdk.NewInt(200000002), sdk.NewInt(100000002) + priceBidAmount1, priceBidAmount2 := sdk.NewInt(200000000), sdk.NewInt(100000000) priceTimestamp1, priceTimestamp2 := now.Add(1*time.Second), now.Add(2*time.Second) // post prices @@ -571,7 +579,8 @@ func TestOracle_PostPrices(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceAmount1, + AskPrice: priceAskAmount1, + BidPrice: priceBidAmount1, ReceivedAt: priceTimestamp1, } @@ -585,7 +594,8 @@ func TestOracle_PostPrices(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceAmount2, + AskPrice: priceAskAmount2, + BidPrice: priceBidAmount2, ReceivedAt: priceTimestamp2, } @@ -599,7 +609,8 @@ func TestOracle_PostPrices(t *testing.T) { // check the last price is the current price { price := app.oracleKeeper.GetCurrentPrice(GetContext(app, true), assetCode) - require.True(t, price.Price.Equal(priceAmount2)) + require.True(t, price.AskPrice.Equal(priceAskAmount2)) + require.True(t, price.BidPrice.Equal(priceBidAmount2)) require.True(t, price.ReceivedAt.Equal(priceTimestamp2)) } @@ -608,7 +619,8 @@ func TestOracle_PostPrices(t *testing.T) { ctx := GetContext(app, true) rawPrices := app.oracleKeeper.GetRawPrices(ctx, assetCode, ctx.BlockHeight()-1) require.Len(t, rawPrices, 1) - require.True(t, priceAmount2.Equal(rawPrices[0].Price)) + require.True(t, priceAskAmount2.Equal(rawPrices[0].AskPrice)) + require.True(t, priceBidAmount2.Equal(rawPrices[0].BidPrice)) require.True(t, priceTimestamp2.Equal(rawPrices[0].ReceivedAt)) require.Equal(t, assetCode, rawPrices[0].AssetCode) require.Equal(t, genAddrs[0], rawPrices[0].OracleAddress) @@ -618,7 +630,8 @@ func TestOracle_PostPrices(t *testing.T) { // check posting prices from different oracles { now := time.Now() - priceValues := []sdk.Int{sdk.NewInt(200000000), sdk.NewInt(100000000), sdk.NewInt(300000000)} + priceAskValues := []sdk.Int{sdk.NewInt(200000002), sdk.NewInt(100000001), sdk.NewInt(300000003)} + priceBidValues := []sdk.Int{sdk.NewInt(200000000), sdk.NewInt(100000000), sdk.NewInt(300000000)} priceTimestamps := []time.Time{now.Add(1 * time.Second), now.Add(2 * time.Second), now.Add(3 * time.Second)} // post prices @@ -629,7 +642,8 @@ func TestOracle_PostPrices(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceValues[0], + AskPrice: priceAskValues[0], + BidPrice: priceBidValues[0], ReceivedAt: priceTimestamps[0], } @@ -643,7 +657,8 @@ func TestOracle_PostPrices(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceValues[1], + AskPrice: priceAskValues[1], + BidPrice: priceBidValues[1], ReceivedAt: priceTimestamps[1], } @@ -657,7 +672,8 @@ func TestOracle_PostPrices(t *testing.T) { msg := oracle.MsgPostPrice{ From: senderAcc.GetAddress(), AssetCode: assetCode, - Price: priceValues[2], + AskPrice: priceAskValues[2], + BidPrice: priceBidValues[2], ReceivedAt: priceTimestamps[2], } @@ -671,7 +687,8 @@ func TestOracle_PostPrices(t *testing.T) { // check the last price is the median price { price := app.oracleKeeper.GetCurrentPrice(GetContext(app, true), assetCode) - require.True(t, price.Price.Equal(priceValues[0])) + require.True(t, price.AskPrice.Equal(priceAskValues[0])) + require.True(t, price.BidPrice.Equal(priceBidValues[0])) require.True(t, price.ReceivedAt.Equal(priceTimestamps[0])) } @@ -681,7 +698,8 @@ func TestOracle_PostPrices(t *testing.T) { rawPrices := app.oracleKeeper.GetRawPrices(ctx, assetCode, ctx.BlockHeight()-1) require.Len(t, rawPrices, 3) for i, rawPrice := range rawPrices { - require.True(t, priceValues[i].Equal(rawPrice.Price)) + require.True(t, priceAskValues[i].Equal(rawPrice.AskPrice)) + require.True(t, priceBidValues[i].Equal(rawPrice.BidPrice)) require.True(t, priceTimestamps[i].Equal(rawPrice.ReceivedAt)) require.Equal(t, assetCode, rawPrice.AssetCode) require.Equal(t, genAddrs[i], rawPrice.OracleAddress) diff --git a/go.mod b/go.mod index 02c5a87a..430c9606 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/swaggo/http-swagger v0.0.0-20200308142732-58ac5e232fba github.com/swaggo/swag v1.6.7 github.com/tendermint/go-amino v0.15.1 + github.com/shopspring/decimal v1.2.0 github.com/tendermint/tendermint v0.33.7 github.com/tendermint/tm-db v0.5.1 golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect diff --git a/go.sum b/go.sum index 9f603923..39101f49 100644 --- a/go.sum +++ b/go.sum @@ -645,6 +645,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= diff --git a/helpers/input_parsers.go b/helpers/input_parsers.go index 3342b682..75dc6a85 100644 --- a/helpers/input_parsers.go +++ b/helpers/input_parsers.go @@ -115,6 +115,16 @@ func ParseSdkIntParam(argName, argValue string, paramType ParamType) (sdk.Int, e return v, nil } +// ParseSdkDecParam parses sdk.Dec param. +func ParseSdkDecParam(argName, argValue string, paramType ParamType) (sdk.Dec, error) { + v, err := sdk.NewDecFromStr(argValue) + if err != nil { + return sdk.Dec{}, fmt.Errorf("%s %s %q: parsing Dec: failed(%s)", argName, paramType, argValue, err.Error()) + } + + return v, nil +} + // ParseSdkIntParam parses sdk.Uint param. func ParseSdkUintParam(argName, argValue string, paramType ParamType) (sdk.Uint, error) { vInt, ok := sdk.NewIntFromString(argValue) diff --git a/helpers/tests/clitester/cli_tester_queries.go b/helpers/tests/clitester/cli_tester_queries.go index 68216e30..5c268456 100644 --- a/helpers/tests/clitester/cli_tester_queries.go +++ b/helpers/tests/clitester/cli_tester_queries.go @@ -98,8 +98,8 @@ func (ct *CLITester) QueryOracleRawPrices(assetCode dnTypes.AssetCode, blockHeig return q, resObj } -func (ct *CLITester) QueryOraclePrice(assetCode dnTypes.AssetCode) (*QueryRequest, *oracle.CurrentPrice) { - resObj := &oracle.CurrentPrice{} +func (ct *CLITester) QueryOraclePrice(assetCode dnTypes.AssetCode) (*QueryRequest, *oracle.CurrentAssetPrice) { + resObj := &oracle.CurrentAssetPrice{} q := ct.newQueryRequest(resObj) q.SetCmd( "oracle", diff --git a/helpers/tests/clitester/cli_tester_rest.go b/helpers/tests/clitester/cli_tester_rest.go index 899e3753..fb1929f1 100644 --- a/helpers/tests/clitester/cli_tester_rest.go +++ b/helpers/tests/clitester/cli_tester_rest.go @@ -220,9 +220,9 @@ func (ct *CLITester) RestQueryOracleRawPrices(assetCode dnTypes.AssetCode, block return r, respMsg } -func (ct *CLITester) RestQueryOraclePrice(assetCode dnTypes.AssetCode) (*RestRequest, *oracle.CurrentPrice) { +func (ct *CLITester) RestQueryOraclePrice(assetCode dnTypes.AssetCode) (*RestRequest, *oracle.CurrentAssetPrice) { reqSubPath := fmt.Sprintf("%s/currentprice/%s", oracle.ModuleName, assetCode.String()) - respMsg := &oracle.CurrentPrice{} + respMsg := &oracle.CurrentAssetPrice{} r := ct.newRestRequest().SetQuery("GET", reqSubPath, nil, nil, respMsg) @@ -437,14 +437,14 @@ func (ct *CLITester) RestQueryVMLcsView(address, movePath, viewRequest string) ( return r, respMsg } -func (ct *CLITester) RestTxOraclePostPrice(accName string, assetCode dnTypes.AssetCode, price sdk.Int, receivedAt time.Time) (*RestRequest, *sdk.TxResponse) { +func (ct *CLITester) RestTxOraclePostPrice(accName string, assetCode dnTypes.AssetCode, askPrice, bidPrice sdk.Int, receivedAt time.Time) (*RestRequest, *sdk.TxResponse) { accInfo := ct.Accounts[accName] require.NotNil(ct.t, accInfo, "account %s: not found", accName) accQuery, acc := ct.QueryAccount(accInfo.Address) accQuery.CheckSucceeded() - msg := oracle.NewMsgPostPrice(acc.Address, assetCode, price, receivedAt) + msg := oracle.NewMsgPostPrice(acc.Address, assetCode, askPrice, bidPrice, receivedAt) return ct.newRestTxRequest(accName, acc, msg, false) } diff --git a/helpers/tests/clitester/cli_tester_txs.go b/helpers/tests/clitester/cli_tester_txs.go index b473989c..d826f54d 100644 --- a/helpers/tests/clitester/cli_tester_txs.go +++ b/helpers/tests/clitester/cli_tester_txs.go @@ -156,14 +156,15 @@ func (ct *CLITester) TxOracleSetOracles(nomineeAddress string, assetCode dnTypes return r } -func (ct *CLITester) TxOraclePostPrice(nomineeAddress string, assetCode dnTypes.AssetCode, price sdk.Int, receivedAt time.Time) *TxRequest { +func (ct *CLITester) TxOraclePostPrice(nomineeAddress string, assetCode dnTypes.AssetCode, askPrice, bidPrice sdk.Int, receivedAt time.Time) *TxRequest { r := ct.newTxRequest() r.SetCmd( "oracle", nomineeAddress, "postprice", assetCode.String(), - price.String(), + askPrice.String(), + bidPrice.String(), strconv.FormatInt(receivedAt.Unix(), 10), ) diff --git a/helpers/types/asset_code.go b/helpers/types/asset_code.go index e0fd022a..d827acc8 100644 --- a/helpers/types/asset_code.go +++ b/helpers/types/asset_code.go @@ -1,5 +1,10 @@ package types +import ( + "fmt" + "strings" +) + type AssetCode string // Validate validates asset code. @@ -11,3 +16,13 @@ func (a AssetCode) Validate() error { func (a AssetCode) String() string { return string(a) } + +// ReverseCode reverses asset code. +// Will panic if use it with the wrong format asset code. +func (a AssetCode) ReverseCode() AssetCode { + parts := strings.Split(a.String(), "_") + if len(parts) != 2 { + panic(fmt.Errorf("wrong asset code format: %s", a)) + } + return AssetCode(parts[1] + "_" + parts[0]) +} diff --git a/x/oracle/alias.go b/x/oracle/alias.go index 62775aad..d69edab6 100644 --- a/x/oracle/alias.go +++ b/x/oracle/alias.go @@ -16,6 +16,7 @@ type ( Oracle = types.Oracle Oracles = types.Oracles CurrentPrice = types.CurrentPrice + CurrentAssetPrice = types.CurrentAssetPrice CurrentPrices = types.CurrentPrices PostedPrice = types.PostedPrice Keeper = keeper.Keeper @@ -39,8 +40,11 @@ const ( EventTypePrice = types.EventTypePrice // AttributeAssetCode = types.AttributeAssetCode - AttributePrice = types.AttributePrice + AttributeAskPrice = types.AttributeAskPrice + AttributeBidPrice = types.AttributeBidPrice AttributeReceivedAt = types.AttributeReceivedAt + // + PricePrecision = types.PricePrecision ) var ( diff --git a/x/oracle/client/cli/queries.go b/x/oracle/client/cli/queries.go index bd03bcee..dcb171e1 100644 --- a/x/oracle/client/cli/queries.go +++ b/x/oracle/client/cli/queries.go @@ -58,7 +58,7 @@ func GetCmdCurrentPrice(queryRoute string, cdc *codec.Codec) *cobra.Command { return err } - var out types.CurrentPrice + var out types.CurrentAssetPrice cdc.MustUnmarshalJSON(res, &out) return cliCtx.PrintOutput(out) diff --git a/x/oracle/client/cli/tx.go b/x/oracle/client/cli/tx.go index 030659e7..4cf56c0d 100644 --- a/x/oracle/client/cli/tx.go +++ b/x/oracle/client/cli/tx.go @@ -15,10 +15,10 @@ import ( // GetCmdPostPrice returns tx command for posting price for a particular asset. func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "postprice [assetCode] [price] [receivedAt]", + Use: "postprice [assetCode] [askPrice] [bidPrice] [receivedAt]", Short: "Post the latest price for a particular asset", - Example: "postprice eth_usdt 100 1594732456", - Args: cobra.ExactArgs(3), + Example: "postprice eth_usdt 100 95 1594732456", + Args: cobra.ExactArgs(4), RunE: func(cmd *cobra.Command, args []string) error { cliCtx, txBuilder := helpers.GetTxCmdCtx(cdc, cmd.InOrStdin()) @@ -33,18 +33,23 @@ func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { return err } - price, err := helpers.ParseSdkIntParam("price", args[1], helpers.ParamTypeCliArg) + askPrice, err := helpers.ParseSdkIntParam("askPrice", args[1], helpers.ParamTypeCliArg) if err != nil { return err } - receivedAt, err := helpers.ParseUnixTimestamp("receivedAt", args[2], helpers.ParamTypeCliArg) + bidPrice, err := helpers.ParseSdkIntParam("bidPrice", args[2], helpers.ParamTypeCliArg) + if err != nil { + return err + } + + receivedAt, err := helpers.ParseUnixTimestamp("receivedAt", args[3], helpers.ParamTypeCliArg) if err != nil { return err } // prepare and send message - msg := types.NewMsgPostPrice(fromAddr, assetCode, price, receivedAt) + msg := types.NewMsgPostPrice(fromAddr, assetCode, askPrice, bidPrice, receivedAt) if err := msg.ValidateBasic(); err != nil { return err } @@ -54,7 +59,8 @@ func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { } helpers.BuildCmdHelp(cmd, []string{ "asset code symbol", - "price [int]", + "askPrice [int]", + "bidPrice [int]", "price received at UNIX timestamp in seconds [int]", }) diff --git a/x/oracle/client/rest/rest.go b/x/oracle/client/rest/rest.go index 3720e201..8b2ab6e5 100644 --- a/x/oracle/client/rest/rest.go +++ b/x/oracle/client/rest/rest.go @@ -23,8 +23,10 @@ type PostPriceReq struct { BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` // AssetCode AssetCode string `json:"asset_code" example:"btc_xfi"` - // Price in big.Int format - Price string `json:"price" example:"100"` + // AskPrice in sdk.Int format + AskPrice string `json:"ask_price" example:"100"` + // BidPrice in sdk.Int format + BidPrice string `json:"bid_price" example:"99"` // Timestamp price createdAt ReceivedAt string `json:"received_at" format:"RFC 3339" example:"2020-03-27T13:45:15.293426Z"` } @@ -75,7 +77,13 @@ func postPriceHandler(cliCtx context.CLIContext) http.HandlerFunc { return } - price, err := helpers.ParseSdkIntParam("price", req.Price, helpers.ParamTypeRestRequest) + askPrice, err := helpers.ParseSdkIntParam("askPrice", req.AskPrice, helpers.ParamTypeRestRequest) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + bidPrice, err := helpers.ParseSdkIntParam("bidPrice", req.BidPrice, helpers.ParamTypeRestRequest) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return @@ -88,7 +96,7 @@ func postPriceHandler(cliCtx context.CLIContext) http.HandlerFunc { } // create the message - msg := types.NewMsgPostPrice(addr, assetCode, price, receivedAt) + msg := types.NewMsgPostPrice(addr, assetCode, askPrice, bidPrice, receivedAt) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/oracle/handler.go b/x/oracle/handler.go index ce70cbf9..c8b02b4f 100644 --- a/x/oracle/handler.go +++ b/x/oracle/handler.go @@ -42,7 +42,7 @@ func handleMsgPostPrice(ctx sdk.Context, k Keeper, msg MsgPostPrice) (*sdk.Resul return nil, sdkErrors.Wrap(ErrInvalidOracle, msg.From.String()) } - if _, err := k.SetPrice(ctx, msg.From, msg.AssetCode, msg.Price, msg.ReceivedAt); err != nil { + if _, err := k.SetPrice(ctx, msg.From, msg.AssetCode, msg.AskPrice, msg.BidPrice, msg.ReceivedAt); err != nil { return nil, err } diff --git a/x/oracle/internal/keeper/genesis_test.go b/x/oracle/internal/keeper/genesis_test.go index 50a357ed..dae0d399 100644 --- a/x/oracle/internal/keeper/genesis_test.go +++ b/x/oracle/internal/keeper/genesis_test.go @@ -42,7 +42,7 @@ func TestOracleKeeper_Genesis_Init(t *testing.T) { } cpList := types.CurrentPrices{ - NewMockCurrentPrice("btc_xfi", 100), + NewMockCurrentPrice("btc_xfi", 100, 99), } state := types.GenesisState{ @@ -79,10 +79,10 @@ func TestOracleKeeper_Genesis_Init(t *testing.T) { } cpList := types.CurrentPrices{ - NewMockCurrentPrice("btc_xfi", 100), - NewMockCurrentPrice("eth_xfi", 200), - NewMockCurrentPrice("xfi_btc", 300), - NewMockCurrentPrice("usdt_xfi", 400), + NewMockCurrentPrice("btc_xfi", 100, 99), + NewMockCurrentPrice("eth_xfi", 200, 199), + NewMockCurrentPrice("xfi_btc", 300, 298), + NewMockCurrentPrice("usdt_xfi", 400, 389), } state := types.GenesisState{ @@ -111,16 +111,19 @@ func TestOracleKeeper_Genesis_Init(t *testing.T) { require.Equal(t, len(exportedState.CurrentPrices), len(state.CurrentPrices)) // checking all of items existing in the export - sumPrices := sdk.NewIntFromUint64(0) + sumAskPrices, sumBidPrices := sdk.NewInt(0), sdk.NewInt(0) for _, i := range exportedState.CurrentPrices { - sumPrices = sumPrices.Add(i.Price) + sumAskPrices = sumAskPrices.Add(i.AskPrice) + sumBidPrices = sumBidPrices.Add(i.BidPrice) } - sumPricesInitial := sdk.NewIntFromUint64(0) + sumAskPricesInitial, sumBidPricesInitial := sdk.NewInt(0), sdk.NewInt(0) for _, i := range state.CurrentPrices { - sumPricesInitial = sumPricesInitial.Add(i.Price) + sumAskPricesInitial = sumAskPricesInitial.Add(i.AskPrice) + sumBidPricesInitial = sumBidPricesInitial.Add(i.BidPrice) } - require.Equal(t, sumPrices, sumPricesInitial) + require.Equal(t, sumAskPrices, sumAskPricesInitial) + require.Equal(t, sumBidPrices, sumBidPricesInitial) } } diff --git a/x/oracle/internal/keeper/price.go b/x/oracle/internal/keeper/price.go index 2f7ef492..ac61a87b 100644 --- a/x/oracle/internal/keeper/price.go +++ b/x/oracle/internal/keeper/price.go @@ -68,60 +68,86 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context) error { assetCode := v.AssetCode rawPrices := k.GetRawPrices(ctx, assetCode, ctx.BlockHeight()) + var ( + medianAskPrice sdk.Int + medianBidPrice sdk.Int + medianReceivedAt time.Time + ) + l := len(rawPrices) - var medianPrice sdk.Int - var medianReceivedAt time.Time + // TODO make threshold for acceptance (ie. require 51% of oracles to have posted valid prices if l == 0 { // Error if there are no valid prices in the raw oracle //return types.ErrNoValidPrice(k.codespace) - medianPrice = sdk.ZeroInt() + medianAskPrice, medianBidPrice = sdk.ZeroInt(), sdk.ZeroInt() } else if l == 1 { // Return immediately if there's only one price - medianPrice, medianReceivedAt = rawPrices[0].Price, rawPrices[0].ReceivedAt + medianAskPrice, medianBidPrice = rawPrices[0].AskPrice, rawPrices[0].BidPrice + medianReceivedAt = rawPrices[0].ReceivedAt } else { - // sort the prices - sort.Slice(rawPrices, func(i, j int) bool { - return rawPrices[i].Price.LT(rawPrices[j].Price) + bidRawPrices := make([]types.PostedPrice, len(rawPrices)) + copy(bidRawPrices, rawPrices) + askRawPrices := rawPrices + + // sort the prices by ask and bid + sort.Slice(askRawPrices, func(i, j int) bool { + return askRawPrices[i].AskPrice.LT(askRawPrices[j].AskPrice) + }) + sort.Slice(bidRawPrices, func(i, j int) bool { + return bidRawPrices[i].BidPrice.LT(bidRawPrices[j].BidPrice) }) // If there's an even number of prices if l%2 == 0 { // TODO make sure this is safe. // Since it's a price and not a balance, division with precision loss is OK. - price1 := rawPrices[l/2-1].Price - price2 := rawPrices[l/2].Price - sum := price1.Add(price2) + a1 := askRawPrices[l/2-1].AskPrice + a2 := askRawPrices[l/2].AskPrice + sumA := a1.Add(a2) + + b1 := bidRawPrices[l/2-1].BidPrice + b2 := bidRawPrices[l/2].BidPrice + sumB := b1.Add(b2) + divsor := sdk.NewInt(2) - medianPrice = sum.Quo(divsor) + medianAskPrice = sumA.Quo(divsor) + medianBidPrice = sumB.Quo(divsor) medianReceivedAt = ctx.BlockTime().UTC() } else { // integer division, so we'll get an integer back, rounded down - medianPrice, medianReceivedAt = rawPrices[l/2].Price, rawPrices[l/2].ReceivedAt + medianAskPrice, medianBidPrice = askRawPrices[l/2].AskPrice, bidRawPrices[l/2].BidPrice + medianReceivedAt = rawPrices[l/2].ReceivedAt } } // check if there is no rawPrices or medianPrice is invalid - if medianPrice.IsZero() { + if medianAskPrice.IsZero() || medianBidPrice.IsZero() { continue } // check new price for the asset appeared, no need to update after every block oldPrice := k.GetCurrentPrice(ctx, assetCode) - if oldPrice.AssetCode != "" && oldPrice.Price.Equal(medianPrice) { + if oldPrice.AssetCode != "" && oldPrice.AskPrice.Equal(medianAskPrice) && oldPrice.BidPrice.Equal(medianBidPrice) { continue } // set the new price for the asset newPrice := types.CurrentPrice{ AssetCode: assetCode, - Price: medianPrice, + AskPrice: medianAskPrice, + BidPrice: medianBidPrice, ReceivedAt: medianReceivedAt, } k.addCurrentPrice(ctx, newPrice) // save price to VM storage - priceVmAccessPath, priceVmValue := types.NewResPriceStorageValuesPanic(newPrice.AssetCode, newPrice.Price) + priceVmAccessPath, priceVmValue := types.NewResPriceStorageValuesPanic(newPrice.AssetCode, newPrice.AskPrice) + k.vmKeeper.SetValue(ctx, priceVmAccessPath, priceVmValue) + + // also save reversed asset code price to VM storage + newPriceReversed := newPrice.GetReversedAssetCurrentPrice() + priceVmAccessPath, priceVmValue = types.NewResPriceStorageValuesPanic(newPriceReversed.AssetCode, newPriceReversed.AskPrice) k.vmKeeper.SetValue(ctx, priceVmAccessPath, priceVmValue) // emit event @@ -154,7 +180,8 @@ func (k Keeper) SetPrice( ctx sdk.Context, oracle sdk.AccAddress, assetCode dnTypes.AssetCode, - price sdk.Int, + askPrice sdk.Int, + bidPrice sdk.Int, receivedAt time.Time) (types.PostedPrice, error) { k.modulePerms.AutoCheck(types.PermWrite) @@ -181,11 +208,13 @@ func (k Keeper) SetPrice( if found { prices[index] = types.PostedPrice{ AssetCode: assetCode, OracleAddress: oracle, - Price: price, ReceivedAt: receivedAt} + AskPrice: askPrice, BidPrice: bidPrice, + ReceivedAt: receivedAt} } else { prices = append(prices, types.PostedPrice{ AssetCode: assetCode, OracleAddress: oracle, - Price: price, ReceivedAt: receivedAt}) + AskPrice: askPrice, BidPrice: bidPrice, + ReceivedAt: receivedAt}) index = len(prices) - 1 } diff --git a/x/oracle/internal/keeper/price_test.go b/x/oracle/internal/keeper/price_test.go index cd5f7116..0cd00f1e 100644 --- a/x/oracle/internal/keeper/price_test.go +++ b/x/oracle/internal/keeper/price_test.go @@ -14,10 +14,11 @@ import ( "github.com/dfinance/dnode/x/oracle/internal/types" ) -func NewMockCurrentPrice(assetCode string, price uint64) types.CurrentPrice { +func NewMockCurrentPrice(assetCode string, ask, bid int64) types.CurrentPrice { return types.CurrentPrice{ AssetCode: dnTypes.AssetCode(assetCode), - Price: sdk.NewIntFromUint64(price), + AskPrice: sdk.NewInt(ask), + BidPrice: sdk.NewInt(bid), ReceivedAt: time.Now(), } } @@ -65,6 +66,7 @@ func TestOracleKeeper_SetPrice(t *testing.T) { { _, err := keeper.SetPrice( ctx, input.addresses[0], input.stdAssetCode, + sdk.NewInt(33000001), sdk.NewInt(33000000), header.Time) require.NoError(t, err) @@ -72,32 +74,37 @@ func TestOracleKeeper_SetPrice(t *testing.T) { // Get raw prices rawPrices := keeper.GetRawPrices(ctx, input.stdAssetCode, header.Height) require.Equal(t, len(rawPrices), 1) - require.Equal(t, rawPrices[0].Price.Equal(sdk.NewInt(33000000)), true) + require.Equal(t, rawPrices[0].AskPrice.Equal(sdk.NewInt(33000001)), true) + require.Equal(t, rawPrices[0].BidPrice.Equal(sdk.NewInt(33000000)), true) } // Set price by oracle 2 { _, err := keeper.SetPrice( ctx, input.addresses[1], input.stdAssetCode, + sdk.NewInt(35000005), sdk.NewInt(35000000), header.Time) require.NoError(t, err) rawPrices := keeper.GetRawPrices(ctx, input.stdAssetCode, header.Height) require.Equal(t, len(rawPrices), 2) - require.Equal(t, rawPrices[1].Price.Equal(sdk.NewInt(35000000)), true) + require.Equal(t, rawPrices[1].AskPrice.Equal(sdk.NewInt(35000005)), true) + require.Equal(t, rawPrices[1].BidPrice.Equal(sdk.NewInt(35000000)), true) } // Update Price by oracle 1 { _, err := keeper.SetPrice( ctx, input.addresses[0], input.stdAssetCode, + sdk.NewInt(37000007), sdk.NewInt(37000000), header.Time) require.NoError(t, err) rawPrices := keeper.GetRawPrices(ctx, input.stdAssetCode, header.Height) - require.Equal(t, rawPrices[0].Price.Equal(sdk.NewInt(37000000)), true) + require.Equal(t, rawPrices[0].AskPrice.Equal(sdk.NewInt(37000007)), true) + require.Equal(t, rawPrices[0].BidPrice.Equal(sdk.NewInt(37000000)), true) } } @@ -114,6 +121,7 @@ func TestOracleKeeper_GetRawPrice(t *testing.T) { { _, err := keeper.SetPrice( ctx, input.addresses[0], input.stdAssetCode, + sdk.NewInt(33000033), sdk.NewInt(33000000), header.Time) require.NoError(t, err) @@ -121,7 +129,8 @@ func TestOracleKeeper_GetRawPrice(t *testing.T) { // Get raw prices rawPrices := keeper.GetRawPrices(ctx, input.stdAssetCode, header.Height) require.Equal(t, len(rawPrices), 1) - require.Equal(t, rawPrices[0].Price.Equal(sdk.NewInt(33000000)), true) + require.Equal(t, rawPrices[0].AskPrice.Equal(sdk.NewInt(33000033)), true) + require.Equal(t, rawPrices[0].BidPrice.Equal(sdk.NewInt(33000000)), true) } } @@ -136,14 +145,17 @@ func TestOracleKeeper_CurrentPrice(t *testing.T) { _, _ = keeper.SetPrice( ctx, input.addresses[0], input.stdAssetCode, + sdk.NewInt(33000001), sdk.NewInt(33000000), header.Time) _, _ = keeper.SetPrice( ctx, input.addresses[1], input.stdAssetCode, + sdk.NewInt(35000005), sdk.NewInt(35000000), header.Time) _, _ = keeper.SetPrice( ctx, input.addresses[2], input.stdAssetCode, + sdk.NewInt(34000004), sdk.NewInt(34000000), header.Time) // Set current price @@ -151,23 +163,28 @@ func TestOracleKeeper_CurrentPrice(t *testing.T) { require.NoError(t, err) // Get Current price price := keeper.GetCurrentPrice(ctx, input.stdAssetCode) - require.Equal(t, price.Price.Equal(sdk.NewInt(34000000)), true) + require.Equal(t, price.AskPrice.Equal(sdk.NewInt(34000004)), true) + require.Equal(t, price.BidPrice.Equal(sdk.NewInt(34000000)), true) // Even number of oracles _, _ = keeper.SetPrice( ctx, input.addresses[0], input.stdAssetCode, + sdk.NewInt(33000003), sdk.NewInt(33000000), header.Time) _, _ = keeper.SetPrice( ctx, input.addresses[1], input.stdAssetCode, + sdk.NewInt(35000005), sdk.NewInt(35000000), header.Time) _, _ = keeper.SetPrice( ctx, input.addresses[2], input.stdAssetCode, + sdk.NewInt(34000004), sdk.NewInt(34000000), header.Time) _, _ = keeper.SetPrice( ctx, input.addresses[3], input.stdAssetCode, + sdk.NewInt(36000006), sdk.NewInt(36000000), header.Time) @@ -177,11 +194,18 @@ func TestOracleKeeper_CurrentPrice(t *testing.T) { // Checking GetCurrentPrice method price = keeper.GetCurrentPrice(ctx, input.stdAssetCode) - require.Equal(t, price.Price.Equal(sdk.NewInt(34500000)), true) + ask, ok := sdk.NewIntFromString("34500004") + require.True(t, ok) + require.Equal(t, price.AskPrice.Equal(ask), true) + + bid, ok := sdk.NewIntFromString("34500000") + require.True(t, ok) + require.Equal(t, price.BidPrice.Equal(bid), true) price2 := types.CurrentPrice{ AssetCode: dnTypes.AssetCode("usdt_xfi"), - Price: sdk.NewIntFromUint64(1000000), + AskPrice: sdk.NewInt(1000001), + BidPrice: sdk.NewInt(1000000), ReceivedAt: time.Now().Add(-1 * time.Hour), } @@ -193,5 +217,6 @@ func TestOracleKeeper_CurrentPrice(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(cpList)) require.Equal(t, cpList[0].AssetCode, price.AssetCode) - require.Equal(t, cpList[0].Price.Add(cpList[1].Price), price.Price.Add(price2.Price)) + require.Equal(t, cpList[0].AskPrice.Add(cpList[1].AskPrice), price.AskPrice.Add(price2.AskPrice)) + require.Equal(t, cpList[0].BidPrice.Add(cpList[1].BidPrice), price.BidPrice.Add(price2.BidPrice)) } diff --git a/x/oracle/internal/keeper/querier.go b/x/oracle/internal/keeper/querier.go index e29520ff..17598bd6 100644 --- a/x/oracle/internal/keeper/querier.go +++ b/x/oracle/internal/keeper/querier.go @@ -31,12 +31,32 @@ func NewQuerier(keeper Keeper) sdk.Querier { // queryCurrentPrice handles currentPrice query. Takes an [assetCode] and returns CurrentPrice for that asset. func queryCurrentPrice(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, error) { assetCode := dnTypes.AssetCode(path[0]) + var isReversedAsset bool + + currentAsset := assetCode if _, found := keeper.GetAsset(ctx, assetCode); !found { - return []byte{}, sdkErrors.Wrap(sdkErrors.ErrUnknownRequest, "asset not found") + reversedAssetCode := assetCode.ReverseCode() + + if _, found := keeper.GetAsset(ctx, reversedAssetCode); !found { + return []byte{}, sdkErrors.Wrap(sdkErrors.ErrUnknownRequest, "reversed asset not found") + } + + currentAsset = reversedAssetCode + isReversedAsset = true + } + + currentPrice := keeper.GetCurrentPrice(ctx, currentAsset) + if isReversedAsset && currentPrice.AssetCode != "" { + currentPrice = currentPrice.GetReversedAssetCurrentPrice() + } + + out := types.CurrentAssetPrice{ + AssetCode: currentPrice.AssetCode, + Price: currentPrice.AskPrice, + ReceivedAt: currentPrice.ReceivedAt, } - currentPrice := keeper.GetCurrentPrice(ctx, assetCode) - bz, err := codec.MarshalJSONIndent(keeper.cdc, currentPrice) + bz, err := codec.MarshalJSONIndent(keeper.cdc, out) if err != nil { return nil, sdkErrors.Wrapf(types.ErrInternal, "currentPrice marshal: %v", err) } diff --git a/x/oracle/internal/keeper/querier_test.go b/x/oracle/internal/keeper/querier_test.go index 38b93f74..020d1734 100644 --- a/x/oracle/internal/keeper/querier_test.go +++ b/x/oracle/internal/keeper/querier_test.go @@ -24,6 +24,12 @@ func TestOracleKeeper_QueryCurrentPrice(t *testing.T) { require.NoError(t, err) } + // get current reversed price ok + { + _, err := queryCurrentPrice(ctx, []string{input.stdAssetCode.ReverseCode().String()}, abci.RequestQuery{}, keeper) + require.NoError(t, err) + } + // wrong asset code { _, err := queryCurrentPrice(ctx, []string{"wrong_asset"}, abci.RequestQuery{}, keeper) diff --git a/x/oracle/internal/types/events.go b/x/oracle/internal/types/events.go index 0b1b509b..1fd18882 100644 --- a/x/oracle/internal/types/events.go +++ b/x/oracle/internal/types/events.go @@ -11,7 +11,8 @@ const ( EventTypePrice = ModuleName + ".price" // AttributeAssetCode = "asset_code" - AttributePrice = "price" + AttributeAskPrice = "ask_price" + AttributeBidPrice = "bid_price" AttributeReceivedAt = "received_at" ) @@ -26,7 +27,8 @@ func NewAssetAddedEvent(asset Asset) sdk.Event { func NewPriceEvent(price CurrentPrice) sdk.Event { return sdk.NewEvent(EventTypePrice, sdk.NewAttribute(AttributeAssetCode, price.AssetCode.String()), - sdk.NewAttribute(AttributePrice, price.Price.String()), + sdk.NewAttribute(AttributeAskPrice, price.AskPrice.String()), + sdk.NewAttribute(AttributeBidPrice, price.BidPrice.String()), sdk.NewAttribute(AttributeReceivedAt, strconv.FormatInt(price.ReceivedAt.Unix(), 10)), ) } diff --git a/x/oracle/internal/types/genesis_test.go b/x/oracle/internal/types/genesis_test.go index a2bd7e31..1ee309c0 100644 --- a/x/oracle/internal/types/genesis_test.go +++ b/x/oracle/internal/types/genesis_test.go @@ -12,16 +12,16 @@ import ( func getTestGenesisState() GenesisState { return GenesisState{ Params: DefaultParams(), - CurrentPrices: CurrentPrices{NewMockCurrentPrice("btc_xfi", 10000)}, + CurrentPrices: CurrentPrices{NewMockCurrentPrice("btc_xfi", 10001, 1000)}, } } func TestOracles_Genesis_Valid(t *testing.T) { //validateGenesis ok { - order1 := NewMockCurrentPrice("btc_xfi", 10000) - order2 := NewMockCurrentPrice("eth_xfi", 20000) - order3 := NewMockCurrentPrice("xfi_btc", 30000) + order1 := NewMockCurrentPrice("btc_xfi", 10001, 1000) + order2 := NewMockCurrentPrice("eth_xfi", 20002, 2000) + order3 := NewMockCurrentPrice("xfi_btc", 30003, 3000) orderT := &order3 order4 := *orderT diff --git a/x/oracle/internal/types/msg_post_price.go b/x/oracle/internal/types/msg_post_price.go index aa6359b1..7b98b433 100644 --- a/x/oracle/internal/types/msg_post_price.go +++ b/x/oracle/internal/types/msg_post_price.go @@ -16,8 +16,10 @@ type MsgPostPrice struct { From sdk.AccAddress `json:"from" yaml:"from"` // Asset code AssetCode dnTypes.AssetCode `json:"asset_code" yaml:"asset_code"` - // RawPrice - Price sdk.Int `json:"price" yaml:"price"` + // AskPrice + AskPrice sdk.Int `json:"ask_price" yaml:"ask_price"` + // BidPrice + BidPrice sdk.Int `json:"bid_price" yaml:"bid_price"` // ReceivedAt time in UNIX timestamp format [seconds] ReceivedAt time.Time `json:"received_at" yaml:"received_at"` } @@ -36,13 +38,18 @@ func (msg MsgPostPrice) ValidateBasic() error { if err := msg.AssetCode.Validate(); err != nil { return sdkErrors.Wrapf(ErrInternal, "invalid assetCode: value (%s), error (%v)", msg.AssetCode, err) } - if msg.Price.IsNegative() { - return sdkErrors.Wrap(ErrInternal, "invalid (negative) price") + if msg.AskPrice.IsNegative() { + return sdkErrors.Wrap(ErrInternal, "invalid (negative) ask price") } - if msg.Price.BigInt().BitLen() > PriceBytesLimit*8 { - return sdkErrors.Wrapf(ErrInternal, "out of %d bytes limit for price", PriceBytesLimit) + if msg.AskPrice.BigInt().BitLen() > PriceBytesLimit*8 { + return sdkErrors.Wrapf(ErrInternal, "out of %d bytes limit for AskPrice", PriceBytesLimit) + } + if msg.BidPrice.IsNegative() { + return sdkErrors.Wrap(ErrInternal, "invalid (negative) bid price") + } + if msg.BidPrice.BigInt().BitLen() > PriceBytesLimit*8 { + return sdkErrors.Wrapf(ErrInternal, "out of %d bytes limit for BidPrice", PriceBytesLimit) } - return nil } @@ -57,11 +64,12 @@ func (msg MsgPostPrice) GetSigners() []sdk.AccAddress { } // NewMsgPostPrice creates a new PostPrice message. -func NewMsgPostPrice(from sdk.AccAddress, assetCode dnTypes.AssetCode, price sdk.Int, receivedAt time.Time) MsgPostPrice { +func NewMsgPostPrice(from sdk.AccAddress, assetCode dnTypes.AssetCode, askPrice, bidPrice sdk.Int, receivedAt time.Time) MsgPostPrice { return MsgPostPrice{ From: from, AssetCode: assetCode, - Price: price, + AskPrice: askPrice, + BidPrice: bidPrice, ReceivedAt: receivedAt, } } diff --git a/x/oracle/internal/types/msg_post_price_test.go b/x/oracle/internal/types/msg_post_price_test.go index c5d49664..873e05b7 100644 --- a/x/oracle/internal/types/msg_post_price_test.go +++ b/x/oracle/internal/types/msg_post_price_test.go @@ -3,7 +3,6 @@ package types import ( - "math/big" "testing" "time" @@ -19,13 +18,13 @@ func TestOracleMsg_PostPrice(t *testing.T) { from := sdk.AccAddress([]byte("someName")) assetCode := dnTypes.AssetCode("btc_xfi") - price := sdk.NewInt(30050000) + askPrice := sdk.NewInt(30050005) + bidPrice := sdk.NewInt(30050000) expiry := time.Now() - negativePrice, _ := sdk.NewIntFromString("-1") - bigInt := sdk.NewIntFromBigInt(big.NewInt(0).SetBit(big.NewInt(0), PriceBytesLimit*8, 1)) + negativePrice := sdk.NewInt(-1) t.Run("GetSign", func(t *testing.T) { - target := NewMsgPostPrice(from, assetCode, price, expiry) + target := NewMsgPostPrice(from, assetCode, askPrice, bidPrice, expiry) require.Equal(t, "post_price", target.Type()) require.Equal(t, RouterKey, target.Route()) require.True(t, len(target.GetSignBytes()) > 0) @@ -35,29 +34,24 @@ func TestOracleMsg_PostPrice(t *testing.T) { t.Run("GetSign", func(t *testing.T) { // ok { - msg := NewMsgPostPrice(from, assetCode, price, expiry) + msg := NewMsgPostPrice(from, assetCode, askPrice, bidPrice, expiry) require.NoError(t, msg.ValidateBasic()) } // fail: invalid from { - msg := NewMsgPostPrice(sdk.AccAddress{}, assetCode, price, expiry) + msg := NewMsgPostPrice(sdk.AccAddress{}, assetCode, askPrice, bidPrice, expiry) require.Error(t, msg.ValidateBasic()) } // fail: invalid assetCode { - msg := NewMsgPostPrice(from, "", price, expiry) - require.Error(t, msg.ValidateBasic()) - } - // fail: invalid price over limit - { - msg := NewMsgPostPrice(from, assetCode, bigInt, expiry) + msg := NewMsgPostPrice(from, "", askPrice, bidPrice, expiry) require.Error(t, msg.ValidateBasic()) } // fail: invalid price negative { - msg := NewMsgPostPrice(from, assetCode, negativePrice, expiry) + msg := NewMsgPostPrice(from, assetCode, negativePrice, negativePrice, expiry) require.Error(t, msg.ValidateBasic()) } }) diff --git a/x/oracle/internal/types/price.go b/x/oracle/internal/types/price.go index 813e854c..cd43fa87 100644 --- a/x/oracle/internal/types/price.go +++ b/x/oracle/internal/types/price.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "github.com/shopspring/decimal" "strings" "time" @@ -12,10 +13,40 @@ import ( const ( PriceBytesLimit = 8 + PricePrecision = 8 ) -// CurrentPrice contains meta of the current price for the particular asset. +// CurrentPrice contains meta of the current price for the particular asset with ask and bid prices. type CurrentPrice struct { + // Asset code + AssetCode dnTypes.AssetCode `json:"asset_code" yaml:"asset_code" example:"btc_xfi"` + // AskPrice + AskPrice sdk.Int `json:"ask_price" yaml:"ask_price" swaggertype:"string" example:"1000"` + // BidPrice + BidPrice sdk.Int `json:"bid_price" yaml:"bid_price" swaggertype:"string" example:"1000"` + // UNIX Timestamp price createdAt [sec] + ReceivedAt time.Time `json:"received_at" yaml:"received_at" format:"RFC 3339" example:"2020-03-27T13:45:15.293426Z"` +} + +// GetReversedAssetCurrentPrice returns CurrentPrice for reverted +func (cp CurrentPrice) GetReversedAssetCurrentPrice() CurrentPrice { + reverseInt := func(p sdk.Int) sdk.Int { + decP := decimal.NewFromBigInt(p.BigInt(), -PricePrecision) + decP = decimal.NewFromInt(1).Div(decP) + decP = decP.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(PricePrecision))) + return sdk.NewIntFromBigInt(decP.BigInt()) + } + + return CurrentPrice{ + AssetCode: cp.AssetCode.ReverseCode(), + AskPrice: reverseInt(cp.BidPrice), + BidPrice: reverseInt(cp.AskPrice), + ReceivedAt: cp.ReceivedAt, + } +} + +// CurrentAssetPrice contains meta of the current price for the particular asset. +type CurrentAssetPrice struct { // Asset code AssetCode dnTypes.AssetCode `json:"asset_code" yaml:"asset_code" example:"btc_xfi"` // Price @@ -29,11 +60,17 @@ func (cp CurrentPrice) Valid() error { if err := cp.AssetCode.Validate(); err != nil { return fmt.Errorf("asset_code: %w", err) } - if cp.Price.IsZero() { - return fmt.Errorf("price: is zero") + if cp.AskPrice.IsZero() { + return fmt.Errorf("askPrice: is zero") + } + if cp.AskPrice.IsNegative() { + return fmt.Errorf("askPrice: is negative") } - if cp.Price.IsNegative() { - return fmt.Errorf("price: is negative") + if cp.BidPrice.IsZero() { + return fmt.Errorf("bidPrice: is zero") + } + if cp.BidPrice.IsNegative() { + return fmt.Errorf("bidPrice: is negative") } if cp.ReceivedAt.IsZero() { return fmt.Errorf("received_at: is zero") @@ -44,9 +81,10 @@ func (cp CurrentPrice) Valid() error { func (cp CurrentPrice) String() string { return fmt.Sprintf("CurrentPrice:\n"+ "AssetCode: %s\n"+ - "Price: %s\n"+ + "AskPrice: %s\n"+ + "BidPrice: %s\n"+ "ReceivedAt: %s", - cp.AssetCode, cp.Price, cp.ReceivedAt, + cp.AssetCode, cp.AskPrice, cp.BidPrice, cp.ReceivedAt, ) } @@ -58,8 +96,10 @@ type PostedPrice struct { AssetCode dnTypes.AssetCode `json:"asset_code" yaml:"asset_code" example:"btc_xfi"` // Source oracle address OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address" swaggertype:"string" example:"wallet13jyjuz3kkdvqw8u4qfkwd94emdl3vx394kn07h"` - // Price - Price sdk.Int `json:"price" yaml:"price" swaggertype:"string" example:"1000"` + // AskPrice + AskPrice sdk.Int `json:"ask_price" yaml:"ask_price" swaggertype:"string" example:"1000"` + // BidPrice + BidPrice sdk.Int `json:"bid_price" yaml:"bid_price" swaggertype:"string" example:"1000"` // UNIX Timestamp price receivedAt [sec] ReceivedAt time.Time `json:"received_at" yaml:"received_at" format:"RFC 3339" example:"2020-03-27T13:45:15.293426Z"` } @@ -68,10 +108,11 @@ type PostedPrice struct { func (pp PostedPrice) String() string { return strings.TrimSpace( fmt.Sprintf( - "AssetCode: %s\nOracleAddress: %s\nPrice: %s\nReceivedAt: %s", + "AssetCode: %s\nOracleAddress: %s\nAskPrice: %s\nBidPrice: %s\nReceivedAt: %s", pp.AssetCode, pp.OracleAddress, - pp.Price, + pp.AskPrice, + pp.BidPrice, pp.ReceivedAt, ), ) diff --git a/x/oracle/internal/types/price_test.go b/x/oracle/internal/types/price_test.go index fc236d45..6b173153 100644 --- a/x/oracle/internal/types/price_test.go +++ b/x/oracle/internal/types/price_test.go @@ -12,10 +12,11 @@ import ( dnTypes "github.com/dfinance/dnode/helpers/types" ) -func NewMockCurrentPrice(assetCode string, price int64) CurrentPrice { +func NewMockCurrentPrice(assetCode string, ask, bid int64) CurrentPrice { return CurrentPrice{ AssetCode: dnTypes.AssetCode(assetCode), - Price: sdk.NewInt(price), + AskPrice: sdk.NewInt(ask), + BidPrice: sdk.NewInt(bid), ReceivedAt: time.Now(), } } @@ -23,14 +24,14 @@ func NewMockCurrentPrice(assetCode string, price int64) CurrentPrice { func TestOracle_Price_Valid(t *testing.T) { // ok { - price := NewMockCurrentPrice("btc_xfi", 100) + price := NewMockCurrentPrice("btc_xfi", 101, 100) err := price.Valid() require.Nil(t, err) } // wrong asset code { - price := NewMockCurrentPrice("btcXfi", 100) + price := NewMockCurrentPrice("btcXfi", 101, 100) err := price.Valid() require.Error(t, err) require.Contains(t, err.Error(), "asset_code") @@ -38,25 +39,25 @@ func TestOracle_Price_Valid(t *testing.T) { // wrong price: zero { - price := NewMockCurrentPrice("btc_xfi", 0) + price := NewMockCurrentPrice("btc_xfi", 0, 0) err := price.Valid() require.Error(t, err) - require.Contains(t, err.Error(), "price") + require.Contains(t, err.Error(), "Price") require.Contains(t, err.Error(), "is zero") } // wrong price: negative { - price := NewMockCurrentPrice("btc_xfi", -1) + price := NewMockCurrentPrice("btc_xfi", -1, -1) err := price.Valid() require.Error(t, err) - require.Contains(t, err.Error(), "price") + require.Contains(t, err.Error(), "Price") require.Contains(t, err.Error(), "negative") } // wrong ReceivedAt: zero { - price := NewMockCurrentPrice("btc_xfi", 100) + price := NewMockCurrentPrice("btc_xfi", 100, 99) price.ReceivedAt = time.Time{} err := price.Valid() require.Error(t, err) @@ -64,3 +65,15 @@ func TestOracle_Price_Valid(t *testing.T) { require.Contains(t, err.Error(), "zero") } } + +func TestOracle_Price_GetReversedAssetCurrentPrice(t *testing.T) { + // calculate reverse price ask: 10900.55, bid: 10889.95 + { + price := NewMockCurrentPrice("btc_xfi", 1090055000000, 1088995000000) + rp := price.GetReversedAssetCurrentPrice() + + require.Equal(t, rp.AssetCode.String(), "xfi_btc") + require.Equal(t, rp.AskPrice.String(), "9182") // (1/bid/10^8) * 10^8 + require.Equal(t, rp.BidPrice.String(), "9173") // (1/ask/10^8) * 10^8 + } +} diff --git a/x/vm/internal/keeper/integ_test.go b/x/vm/internal/keeper/integ_test.go index fcf4013e..6382e64e 100644 --- a/x/vm/internal/keeper/integ_test.go +++ b/x/vm/internal/keeper/integ_test.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "math" "strconv" "strings" "testing" @@ -112,6 +113,17 @@ script { } } ` +const oracleReverseAssetPriceScript = ` +script { + use 0x1::Event; + use 0x1::Coins; + + fun main(account: &signer) { + let price = Coins::get_price(); + Event::emit(account, price); + } +} +` const errorScript = ` script { @@ -583,6 +595,7 @@ func TestVMKeeper_DeployModuleTwice(t *testing.T) { func TestVMKeeper_ScriptOracle(t *testing.T) { config := sdk.GetConfig() dnodeConfig.InitBechPrefixes(config) + price := sdk.NewInt(10).Mul(sdk.NewInt(int64(math.Pow10(oracle.PricePrecision)))) input := newTestInput(false) @@ -616,7 +629,7 @@ func TestVMKeeper_ScriptOracle(t *testing.T) { } input.ok.SetParams(input.ctx, okInitParams) - input.ok.SetPrice(input.ctx, addr1, assetCode, sdk.NewInt(100), time.Now()) + input.ok.SetPrice(input.ctx, addr1, assetCode, price, price, time.Now()) input.ok.SetCurrentPrices(input.ctx) gs := getGenesis(t) @@ -625,47 +638,103 @@ func TestVMKeeper_ScriptOracle(t *testing.T) { input.vk.StartDSServer(input.ctx) time.Sleep(2 * time.Second) - bytecodeScript, err := vm_client.Compile(*vmCompiler, &vm_grpc.SourceFile{ - Text: oraclePriceScript, - Address: common_vm.Bech32ToLibra(addr1), - }) - require.NoErrorf(t, err, "can't get code for oracle script: %v", err) + // compile direct asset + { + bytecodeScript, err := vm_client.Compile(*vmCompiler, &vm_grpc.SourceFile{ + Text: oraclePriceScript, + Address: common_vm.Bech32ToLibra(addr1), + }) + require.NoErrorf(t, err, "can't get code for oracle direct asset script: %v", err) - msgScript := types.NewMsgExecuteScript(addr1, bytecodeScript, nil) - err = input.vk.ExecuteScript(input.ctx, msgScript) - require.NoError(t, err) + msgScript := types.NewMsgExecuteScript(addr1, bytecodeScript, nil) + err = input.vk.ExecuteScript(input.ctx, msgScript) + require.NoError(t, err) - events := input.ctx.EventManager().Events() - checkNoEventErrors(events, t) + events := input.ctx.EventManager().Events() + checkNoEventErrors(events, t) - checkEventsContainsEvery(t, events, newKeepEvents()) - require.Len(t, events, 5) - vmEvent := events[4] - require.Len(t, vmEvent.Attributes, 4) - // sender - { - attrIdx := 0 - require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventSender) - require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.StringifySenderAddress(addr1)) - } - // source - { - attrIdx := 1 - require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventSource) - require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.GetEventSourceAttribute(nil)) - } - // type - { - attrIdx := 2 - require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventType) - require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.StringifyEventTypePanic(sdk.NewInfiniteGasMeter(), &vm_grpc.LcsTag{TypeTag: vm_grpc.LcsType_LcsU128})) + checkEventsContainsEvery(t, events, newKeepEvents()) + require.Len(t, events, 5) + vmEvent := events[4] + require.Len(t, vmEvent.Attributes, 4) + // sender + { + attrIdx := 0 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventSender) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.StringifySenderAddress(addr1)) + } + // source + { + attrIdx := 1 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventSource) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.GetEventSourceAttribute(nil)) + } + // type + { + attrIdx := 2 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventType) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.StringifyEventTypePanic(sdk.NewInfiniteGasMeter(), &vm_grpc.LcsTag{TypeTag: vm_grpc.LcsType_LcsU128})) + } + // data + { + attrIdx := 3 + price := sdk.NewIntFromBigInt(price.BigInt()) + priceBz := helpers.BigToBytes(price, 16) // u128 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventData) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, hex.EncodeToString(priceBz)) + } } - // data + + // compile reverse asset { - attrIdx := 3 - priceBz := helpers.BigToBytes(sdk.NewInt(100), 16) // u128 - require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventData) - require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, hex.EncodeToString(priceBz)) + events := input.ctx.EventManager().Events() + bytecodeReverseScript, err := vm_client.Compile(*vmCompiler, &vm_grpc.SourceFile{ + Text: oracleReverseAssetPriceScript, + Address: common_vm.Bech32ToLibra(addr1), + }) + require.NoErrorf(t, err, "can't get code for oracle reverse asset script: %v", err) + + msgScript := types.NewMsgExecuteScript(addr1, bytecodeReverseScript, nil) + err = input.vk.ExecuteScript(input.ctx, msgScript) + require.NoError(t, err) + + events = input.ctx.EventManager().Events() + checkNoEventErrors(events, t) + + checkEventsContainsEvery(t, events, newKeepEvents()) + require.Len(t, events, 8) + vmEvent := events[7] + require.Len(t, vmEvent.Attributes, 4) + // sender + { + attrIdx := 0 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventSender) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.StringifySenderAddress(addr1)) + } + // source + { + attrIdx := 1 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventSource) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.GetEventSourceAttribute(nil)) + } + // type + { + attrIdx := 2 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventType) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, types.StringifyEventTypePanic(sdk.NewInfiniteGasMeter(), &vm_grpc.LcsTag{TypeTag: vm_grpc.LcsType_LcsU128})) + } + // data + { + attrIdx := 3 + // price calculation for price 10.0000 in float: 1/10.000 => 0.0100 (10/100 => 0.0100) + // in test for the Int with 8 digit precision + // 10 0000 0000 reverse price is 1000 0000, + // so 10 0000 0000 / 100 => 1000 0000 + clcPrice := price.Quo(sdk.NewInt(100)) + priceBz := helpers.BigToBytes(clcPrice, 16) // u128 + require.EqualValues(t, vmEvent.Attributes[attrIdx].Key, types.AttributeVmEventData) + require.EqualValues(t, vmEvent.Attributes[attrIdx].Value, hex.EncodeToString(priceBz)) + } } }