diff --git a/app/upgrades.go b/app/upgrades.go index 91699ecbc3..a4dc831a77 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -47,10 +47,8 @@ var upgradesList = []string{ "1.2.1beta", // 1.2.2beta "1.2.2beta", - // 1.2.3beta - "1.2.3beta", - // 1.2.4beta - "1.2.4beta", + // 1.2.2beta-postfix + "1.2.2beta-postfix", } func (app App) RegisterUpgradeHandlers() { diff --git a/go.mod b/go.mod index bac72df8b8..6076e9e990 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.9.0 go.opentelemetry.io/otel/sdk v1.9.0 go.opentelemetry.io/otel/trace v1.9.0 + golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c google.golang.org/grpc v1.50.1 @@ -246,14 +247,14 @@ require ( gitlab.com/bosi/decorder v0.2.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/mod v0.6.0 // indirect golang.org/x/net v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.1.0 // indirect golang.org/x/text v0.4.0 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/tools v0.2.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.66.4 // indirect honnef.co/go/tools v0.3.1 // indirect diff --git a/go.sum b/go.sum index 3b2aeca27a..9de20c85db 100644 --- a/go.sum +++ b/go.sum @@ -1507,8 +1507,9 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1524,6 +1525,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1556,8 +1559,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1912,8 +1915,8 @@ golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlz golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/oracle/price-feeder/README.md b/oracle/price-feeder/README.md index 0f8da23547..692053a0bc 100644 --- a/oracle/price-feeder/README.md +++ b/oracle/price-feeder/README.md @@ -13,7 +13,6 @@ This is a standalone version of [Umee's fantastic work](https://github.com/umee- The list of current supported providers: -- [FIN](https://fin.kujira.app) - [Binance](https://www.binance.com/en) - [MEXC](https://www.mexc.com/) - [Coinbase](https://www.coinbase.com/) @@ -21,7 +20,6 @@ The list of current supported providers: - [Huobi](https://www.huobi.com/en-us/) - [Kraken](https://www.kraken.com/en-us/) - [Okx](https://www.okx.com/) -- [Osmosis](https://app.osmosis.zone/) ## Usage @@ -76,7 +74,6 @@ quote = "USDT" base = "ATOM" providers = [ "kraken", - "osmosis", ] quote = "USD" ``` diff --git a/oracle/price-feeder/config.example.toml b/oracle/price-feeder/config.example.toml index a92ad5c162..6cd1f8f02b 100644 --- a/oracle/price-feeder/config.example.toml +++ b/oracle/price-feeder/config.example.toml @@ -18,7 +18,7 @@ base = "ATOM" providers = [ "binance", "kraken", - "osmosis", + "coinbase", ] quote = "USD" diff --git a/oracle/price-feeder/config/config.go b/oracle/price-feeder/config/config.go index f8367bf56d..b0b556cdd5 100644 --- a/oracle/price-feeder/config/config.go +++ b/oracle/price-feeder/config/config.go @@ -20,11 +20,9 @@ const ( defaultSrvReadTimeout = 15 * time.Second defaultProviderTimeout = 100 * time.Millisecond - ProviderFin = "fin" ProviderKraken = "kraken" ProviderBinance = "binance" ProviderMexc = "mexc" - ProviderOsmosis = "osmosis" ProviderHuobi = "huobi" ProviderOkx = "okx" ProviderGate = "gate" @@ -41,11 +39,9 @@ var ( // SupportedProviders defines a lookup table of all the supported currency API // providers. SupportedProviders = map[string]struct{}{ - ProviderFin: {}, ProviderKraken: {}, ProviderBinance: {}, ProviderMexc: {}, - ProviderOsmosis: {}, ProviderOkx: {}, ProviderHuobi: {}, ProviderGate: {}, diff --git a/oracle/price-feeder/dockerfile/README.md b/oracle/price-feeder/dockerfile/README.md index df179f5966..dbb05f1f04 100644 --- a/oracle/price-feeder/dockerfile/README.md +++ b/oracle/price-feeder/dockerfile/README.md @@ -67,7 +67,7 @@ chain_denom = "uatom" providers = [ "binance", "kraken", - "osmosis", + "coinbase", ] quote = "USD" EOF diff --git a/oracle/price-feeder/oracle/client/client.go b/oracle/price-feeder/oracle/client/client.go index 0ab0d73952..6d535092bb 100644 --- a/oracle/price-feeder/oracle/client/client.go +++ b/oracle/price-feeder/oracle/client/client.go @@ -143,7 +143,7 @@ func (r *passReader) Read(p []byte) (n int, err error) { // Ref: https://github.com/terra-money/oracle-feeder/blob/baef2a4a02f57a2ffeaa207932b2e03d7fb0fb25/feeder/src/vote.ts#L230 func (oc OracleClient) BroadcastTx(nextBlockHeight, timeoutHeight int64, msgs ...sdk.Msg) error { maxBlockHeight := nextBlockHeight + timeoutHeight - lastCheckHeight := nextBlockHeight - 1 + lastCheckHeight := nextBlockHeight - 2 clientCtx, err := oc.CreateClientContext() if err != nil { diff --git a/oracle/price-feeder/oracle/oracle.go b/oracle/price-feeder/oracle/oracle.go index b8a401be34..9ce5dcba59 100644 --- a/oracle/price-feeder/oracle/oracle.go +++ b/oracle/price-feeder/oracle/oracle.go @@ -447,9 +447,6 @@ func NewProvider( providerPairs ...types.CurrencyPair, ) (provider.Provider, error) { switch providerName { - case config.ProviderFin: - return provider.NewFinProvider(endpoint), nil - case config.ProviderBinance: return provider.NewBinanceProvider(ctx, logger, endpoint, providerPairs...) @@ -459,9 +456,6 @@ func NewProvider( case config.ProviderMexc: return provider.NewMexcProvider(ctx, logger, endpoint, providerPairs...) - case config.ProviderOsmosis: - return provider.NewOsmosisProvider(endpoint), nil - case config.ProviderHuobi: return provider.NewHuobiProvider(ctx, logger, endpoint, providerPairs...) diff --git a/oracle/price-feeder/oracle/oracle_test.go b/oracle/price-feeder/oracle/oracle_test.go index c193ffa75a..00dd093d9e 100644 --- a/oracle/price-feeder/oracle/oracle_test.go +++ b/oracle/price-feeder/oracle/oracle_test.go @@ -95,7 +95,7 @@ func (ots *OracleTestSuite) SetupSuite() { Base: "XBT", ChainDenom: "uxbt", Quote: "USDT", - Providers: []string{config.ProviderOsmosis}, + Providers: []string{config.ProviderOkx}, }, { Base: "USDC", @@ -226,7 +226,7 @@ func (ots *OracleTestSuite) TestPrices() { }, }, }, - config.ProviderOsmosis: mockProvider{ + config.ProviderOkx: mockProvider{ prices: map[string]provider.TickerPrice{ "XBTUSDT": { Price: sdk.MustNewDecFromStr("3.717"), @@ -279,7 +279,7 @@ func (ots *OracleTestSuite) TestPrices() { }, }, }, - config.ProviderOsmosis: mockProvider{ + config.ProviderOkx: mockProvider{ prices: map[string]provider.TickerPrice{ "XBTUSDT": { Price: sdk.MustNewDecFromStr("3.717"), @@ -331,7 +331,7 @@ func (ots *OracleTestSuite) TestPrices() { }, }, }, - config.ProviderOsmosis: mockProvider{ + config.ProviderOkx: mockProvider{ prices: map[string]provider.TickerPrice{ "XBTUSDT": { Price: sdk.MustNewDecFromStr("3.717"), diff --git a/oracle/price-feeder/oracle/provider/fin.go b/oracle/price-feeder/oracle/provider/fin.go deleted file mode 100644 index c9c91d4306..0000000000 --- a/oracle/price-feeder/oracle/provider/fin.go +++ /dev/null @@ -1,231 +0,0 @@ -package provider - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "time" - - "github.com/sei-protocol/sei-chain/oracle/price-feeder/config" - "github.com/sei-protocol/sei-chain/oracle/price-feeder/oracle/types" -) - -const ( - finRestURL = "https://api.kujira.app" - finPairsEndpoint = "/api/coingecko/pairs" - finTickersEndpoint = "/api/coingecko/tickers" - finCandlesEndpoint = "/api/trades/candles" - finCandleBinSizeMinutes = 5 - finCandleWindowSizeHours = 240 -) - -var _ Provider = (*FinProvider)(nil) - -type ( - FinProvider struct { - baseURL string - client *http.Client - } - - FinTickers struct { - Tickers []FinTicker `json:"tickers"` - } - - FinTicker struct { - Base string `json:"base_currency"` - Target string `json:"target_currency"` - Symbol string `json:"ticker_id"` - Price string `json:"last_price"` - Volume string `json:"base_volume"` - } - - FinCandles struct { - Candles []FinCandle `json:"candles"` - } - - FinCandle struct { - Bin string `json:"bin"` - Close string `json:"close"` - Volume string `json:"volume"` - } - - FinPairs struct { - Pairs []FinPair `json:"pairs"` - } - - FinPair struct { - Base string `json:"base"` - Target string `json:"target"` - Symbol string `json:"ticker_id"` - Address string `json:"pool_id"` - } -) - -func NewFinProvider(endpoint config.ProviderEndpoint) *FinProvider { - if endpoint.Name == config.ProviderFin { - return &FinProvider{ - baseURL: endpoint.Rest, - client: newDefaultHTTPClient(), - } - } - return &FinProvider{ - baseURL: finRestURL, - client: newDefaultHTTPClient(), - } -} - -func (p FinProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]TickerPrice, error) { - path := fmt.Sprintf("%s%s", p.baseURL, finTickersEndpoint) - tickerResponse, err := p.client.Get(path) - if err != nil { - return nil, fmt.Errorf("FIN tickers request failed: %w", err) - } - defer tickerResponse.Body.Close() - tickerContent, err := io.ReadAll(tickerResponse.Body) - if err != nil { - return nil, fmt.Errorf("FIN tickers response read failed: %w", err) - } - var tickers FinTickers - err = json.Unmarshal(tickerContent, &tickers) - if err != nil { - return nil, fmt.Errorf("FIN tickers response unmarshal failed: %w", err) - } - tickerSymbolPairs := make(map[string]types.CurrencyPair, len(pairs)) - for _, pair := range pairs { - tickerSymbolPairs[pair.Base+"_"+pair.Quote] = pair - } - tickerPrices := make(map[string]TickerPrice, len(pairs)) - for _, ticker := range tickers.Tickers { - pair, ok := tickerSymbolPairs[strings.ToUpper(ticker.Symbol)] - if !ok { - // skip tokens that are not requested - continue - } - _, ok = tickerPrices[pair.String()] - if ok { - return nil, fmt.Errorf("FIN tickers response contained duplicate: %s", ticker.Symbol) - } - tickerPrices[pair.String()] = TickerPrice{ - Price: strToDec(ticker.Price), - Volume: strToDec(ticker.Volume), - } - } - for _, pair := range pairs { - _, ok := tickerPrices[pair.String()] - if !ok { - return nil, fmt.Errorf("FIN ticker price missing for pair: %s", pair.String()) - } - } - return tickerPrices, nil -} - -func (p FinProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]CandlePrice, error) { - pairAddresses, err := p.getFinPairAddresses() - if err != nil { - return nil, fmt.Errorf("FIN pair addresses lookup failed: %w", err) - } - candlePricesPairs := make(map[string][]CandlePrice) - for _, pair := range pairs { - address, ok := pairAddresses[pair.String()] - if !ok { - return nil, fmt.Errorf("FIN contract address lookup failed for pair: %s", pair.String()) - } - candlePricesPairs[pair.String()] = []CandlePrice{} - windowEndTime := time.Now() - windowStartTime := windowEndTime.Add(-finCandleWindowSizeHours * time.Hour) - path := fmt.Sprintf("%s%s?contract=%s&precision=%d&from=%s&to=%s", - p.baseURL, - finCandlesEndpoint, - address, - finCandleBinSizeMinutes, - windowStartTime.Format(time.RFC3339), - windowEndTime.Format(time.RFC3339), - ) - candlesResponse, err := p.client.Get(path) - if err != nil { - return nil, fmt.Errorf("FIN candles request failed: %w", err) - } - defer candlesResponse.Body.Close() - candlesContent, err := io.ReadAll(candlesResponse.Body) - if err != nil { - return nil, fmt.Errorf("FIN candles response read failed: %w", err) - } - var candles FinCandles - err = json.Unmarshal(candlesContent, &candles) - if err != nil { - return nil, fmt.Errorf("FIN candles response unmarshal failed: %w", err) - } - candlePrices := []CandlePrice{} - for _, candle := range candles.Candles { - timeStamp, err := binToTimeStamp(candle.Bin) - if err != nil { - return nil, fmt.Errorf("FIN candle timestamp failed to parse: %s", candle.Bin) - } - candlePrices = append(candlePrices, CandlePrice{ - Price: strToDec(candle.Close), - Volume: strToDec(candle.Volume), - TimeStamp: timeStamp, - }) - } - candlePricesPairs[pair.String()] = candlePrices - } - return candlePricesPairs, nil -} - -func (p FinProvider) GetAvailablePairs() (map[string]struct{}, error) { - finPairs, err := p.getFinPairs() - if err != nil { - return nil, err - } - availablePairs := make(map[string]struct{}, len(finPairs.Pairs)) - for _, pair := range finPairs.Pairs { - pair := types.CurrencyPair{ - Base: strings.ToUpper(pair.Base), - Quote: strings.ToUpper(pair.Target), - } - availablePairs[pair.String()] = struct{}{} - } - return availablePairs, nil -} - -func (p FinProvider) getFinPairs() (FinPairs, error) { - path := fmt.Sprintf("%s%s", p.baseURL, finPairsEndpoint) - pairsResponse, err := p.client.Get(path) - if err != nil { - return FinPairs{}, err - } - defer pairsResponse.Body.Close() - var pairs FinPairs - err = json.NewDecoder(pairsResponse.Body).Decode(&pairs) - if err != nil { - return FinPairs{}, err - } - return pairs, nil -} - -func (p FinProvider) getFinPairAddresses() (map[string]string, error) { - finPairs, err := p.getFinPairs() - if err != nil { - return nil, err - } - pairAddresses := make(map[string]string, len(finPairs.Pairs)) - for _, pair := range finPairs.Pairs { - pairAddresses[strings.ToUpper(pair.Base+pair.Target)] = pair.Address - } - return pairAddresses, nil -} - -// SubscribeCurrencyPairs performs a no-op since fin does not use websockets -func (p FinProvider) SubscribeCurrencyPairs(pairs ...types.CurrencyPair) error { - return nil -} - -func binToTimeStamp(bin string) (int64, error) { - timeParsed, err := time.Parse(time.RFC3339, bin) - if err != nil { - return -1, err - } - return timeParsed.Unix(), nil -} diff --git a/oracle/price-feeder/oracle/provider/fin_test.go b/oracle/price-feeder/oracle/provider/fin_test.go deleted file mode 100644 index de83a19a1b..0000000000 --- a/oracle/price-feeder/oracle/provider/fin_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package provider - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/sei-protocol/sei-chain/oracle/price-feeder/config" - "github.com/sei-protocol/sei-chain/oracle/price-feeder/oracle/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestFinProvider_GetTickerPrices(t *testing.T) { - p := NewFinProvider(config.ProviderEndpoint{}) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/api/coingecko/tickers", req.URL.String()) - resp := `{ - "tickers": [{ - "ask":"0.9640000000", - "base_currency":"KUJI", - "base_volume":"544225.3735890000", - "bid":"0.9450000000", - "high":"0.9830000001", - "last_price":"0.9640001379", - "low":"0.7712884676", - "pool_id":"kujira14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sl4e867", - "target_currency":"axlUSDC", - "target_volume":"480430.1470840000", - "ticker_id":"KUJI_axlUSDC" - }] - }` - rw.Write([]byte(resp)) - })) - defer server.Close() - p.client = server.Client() - p.baseURL = server.URL - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "KUJI", Quote: "AXLUSDC"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr("0.9640001379"), prices["KUJIAXLUSDC"].Price) - require.Equal(t, sdk.MustNewDecFromStr("544225.3735890000"), prices["KUJIAXLUSDC"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/api/coingecko/tickers", req.URL.String()) - resp := `{ - "tickers": [{ - "ask":"0.9640000000", - "base_currency":"KUJI", - "base_volume":"544225.3735890000", - "bid":"0.9450000000", - "high":"0.9830000001", - "last_price":"0.9640001379", - "low":"0.7712884676", - "pool_id":"kujira14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sl4e867", - "target_currency":"axlUSDC", - "target_volume":"480430.1470840000", - "ticker_id":"KUJI_axlUSDC" - }, { - "ask":"1.8750000000", - "base_currency":"EVMOS", - "base_volume":"122.0077927374", - "bid":"1.5110000000", - "high":"1.8650000000", - "last_price":"1.8650000000", - "low":"1.5087335000", - "pool_id":"kujira182nff4ttmvshn6yjlqj5czapfcav9434l2qzz8aahf5pxnyd33tsz30aw6", - "target_currency":"axlUSDC", - "target_volume":"211.3908830000", - "ticker_id":"EVMOS_axlUSDC" - }] - }` - rw.Write([]byte(resp)) - })) - defer server.Close() - p.client = server.Client() - p.baseURL = server.URL - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "KUJI", Quote: "AXLUSDC"}, - types.CurrencyPair{Base: "EVMOS", Quote: "AXLUSDC"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr("0.9640001379"), prices["KUJIAXLUSDC"].Price) - require.Equal(t, sdk.MustNewDecFromStr("544225.3735890000"), prices["KUJIAXLUSDC"].Volume) - require.Equal(t, sdk.MustNewDecFromStr("1.8650000000"), prices["EVMOSAXLUSDC"].Price) - require.Equal(t, sdk.MustNewDecFromStr("122.0077927374"), prices["EVMOSAXLUSDC"].Volume) - }) - - t.Run("invalid_request_bad_response", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/api/coingecko/tickers", req.URL.String()) - rw.Write([]byte(`FOO`)) - })) - defer server.Close() - p.client = server.Client() - p.baseURL = server.URL - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.Error(t, err) - require.Nil(t, prices) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/api/coingecko/tickers", req.URL.String()) - resp := `{ - "tickers": [{ - "ask":"0.9640000000", - "base_currency":"KUJI", - "base_volume":"544225.3735890000", - "bid":"0.9450000000", - "high":"0.9830000001", - "last_price":"0.9640001379", - "low":"0.7712884676", - "pool_id":"kujira14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sl4e867", - "target_currency":"axlUSDC", - "target_volume":"480430.1470840000", - "ticker_id":"KUJI_axlUSDC" - }] - }` - rw.Write([]byte(resp)) - })) - defer server.Close() - p.client = server.Client() - p.baseURL = server.URL - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.Error(t, err) - require.Nil(t, prices) - }) - - t.Run("check_redirect", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - http.Redirect(rw, r, p.baseURL, http.StatusTemporaryRedirect) - })) - defer server.Close() - server.Client().CheckRedirect = preventRedirect - p.client = server.Client() - p.baseURL = server.URL - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.Error(t, err) - require.Nil(t, prices) - }) -} - -func TestFinProvideR_GetCandlePrices(t *testing.T) { - p := NewFinProvider(config.ProviderEndpoint{}) - - t.Run("valid_request_single_candle", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - requestUrl := req.URL.String() - resp := "" - if requestUrl == "/api/coingecko/pairs" { - resp = `{ - "pairs": [ - {"base":"KUJI","pool_id":"kujiraTESTADDRESS","target":"axlUSDC","ticker_id":"KUJI_axlUSDC"} - ] - }` - } else { - require.Equal(t, "/api/trades/candles?contract=kujiraTESTADDRESS", strings.Split(requestUrl, "&")[0]) - resp = `{ - "candles": [ - {"bin":"2022-08-07T14:05:00.000000Z","close":"0.65600004530055243509","high":"0.65600004530055243509","low":"0.65600004530055243509","open":"0.65600004530055243509","volume":"7646000"}, - {"bin":"2022-08-07T14:10:00.000000Z","close":"0.65600004530055243509","high":"0.65600004530055243509","low":"0.65600004530055243509","open":"0.65600004530055243509","volume":"0"}, - {"bin":"2022-08-07T14:15:00.000000Z","close":"0.65928507215810458196","high":"0.65928507215810458196","low":"0.65928507215810458196","open":"0.65928507215810458196","volume":"622000000"} - ] - }` - } - rw.Write([]byte(resp)) - })) - defer server.Close() - p.client = server.Client() - p.baseURL = server.URL - prices, err := p.GetCandlePrices(types.CurrencyPair{Base: "KUJI", Quote: "AXLUSDC"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Len(t, prices["KUJIAXLUSDC"], 3) - require.Equal(t, sdk.MustNewDecFromStr("0.656000045300552435"), prices["KUJIAXLUSDC"][0].Price) - require.Equal(t, sdk.MustNewDecFromStr("0.659285072158104581"), prices["KUJIAXLUSDC"][2].Price) - require.Equal(t, sdk.MustNewDecFromStr("7646000"), prices["KUJIAXLUSDC"][0].Volume) - require.Equal(t, sdk.MustNewDecFromStr("0"), prices["KUJIAXLUSDC"][1].Volume) - require.Equal(t, int64(1659881100), prices["KUJIAXLUSDC"][0].TimeStamp) - require.Equal(t, int64(1659881700), prices["KUJIAXLUSDC"][2].TimeStamp) - }) -} - -func TestFinProvider_GetAvailablePairs(t *testing.T) { - p := NewFinProvider(config.ProviderEndpoint{}) - p.GetAvailablePairs() - - t.Run("valid_available_pair", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/api/coingecko/pairs", req.URL.String()) - resp := `{ - "pairs":[{ - "base":"KUJI", - "pool_id":"kujira14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sl4e867", - "target":"axlUSDC", - "ticker_id":"KUJI_axlUSDC" - }, - { - "base":"wETH", - "pool_id":"kujira1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsqq4jjh", - "target":"axlUSDC", - "ticker_id":"wETH_axlUSDC" - }] - }` - rw.Write([]byte(resp)) - })) - defer server.Close() - p.client = server.Client() - p.baseURL = server.URL - availablePairs, err := p.GetAvailablePairs() - require.Nil(t, err) - _, exist := availablePairs["KUJIAXLUSDC"] - require.True(t, exist) - _, exist = availablePairs["WETHAXLUSDC"] - require.True(t, exist) - }) -} diff --git a/oracle/price-feeder/oracle/provider/gate_test.go b/oracle/price-feeder/oracle/provider/gate_test.go index 459caba235..43bedcdb97 100644 --- a/oracle/price-feeder/oracle/provider/gate_test.go +++ b/oracle/price-feeder/oracle/provider/gate_test.go @@ -13,10 +13,19 @@ import ( ) func TestGateProvider_GetTickerPrices(t *testing.T) { + // use mock provider server + server := NewMockProviderServer() + server.Start() + defer server.Close() + p, err := NewGateProvider( context.TODO(), zerolog.Nop(), - config.ProviderEndpoint{}, + config.ProviderEndpoint{ + Name: config.ProviderGate, + Rest: "", + Websocket: server.GetBaseURL(), + }, types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, ) require.NoError(t, err) @@ -81,10 +90,19 @@ func TestGateProvider_GetTickerPrices(t *testing.T) { } func TestGateProvider_SubscribeCurrencyPairs(t *testing.T) { + // // use mock provider server + server := NewMockProviderServer() + server.Start() + defer server.Close() + p, err := NewGateProvider( context.TODO(), zerolog.Nop(), - config.ProviderEndpoint{}, + config.ProviderEndpoint{ + Name: config.ProviderGate, + Rest: "", + Websocket: server.GetBaseURL(), + }, types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, ) require.NoError(t, err) diff --git a/oracle/price-feeder/oracle/provider/mock_server.go b/oracle/price-feeder/oracle/provider/mock_server.go new file mode 100644 index 0000000000..a0fdfb2378 --- /dev/null +++ b/oracle/price-feeder/oracle/provider/mock_server.go @@ -0,0 +1,107 @@ +package provider + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "time" + + "github.com/gorilla/websocket" +) + +type MockProviderServer struct { + handlerFunc http.HandlerFunc + server *httptest.Server +} + +func NewMockProviderServer() MockProviderServer { + mockProvider := MockProviderServer{} + // default to echo handler + mockProvider.SetHandler(echo) + return mockProvider +} + +func (m *MockProviderServer) SetHandler(handler http.HandlerFunc) { + m.Close() + m.handlerFunc = handler + m.Start() +} + +func (m *MockProviderServer) Start() { + server := httptest.NewUnstartedServer(m.handlerFunc) + server.StartTLS() + m.server = server + m.InjectServerCertificatesIntoDefaultDialer() +} + +func (m *MockProviderServer) Close() { + if m.server != nil { + // restore default dialer + websocket.DefaultDialer = &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 45 * time.Second, + } + m.server.Close() + } +} + +func (m *MockProviderServer) GetBaseURL() string { + if m.server != nil { + return strings.TrimPrefix(m.server.URL, "https://") + } + return "" +} + +func (m *MockProviderServer) GetWebsocketURL() string { + if m.server != nil { + return "wss" + strings.TrimPrefix(m.server.URL, "https") + } + return "" +} + +func (m *MockProviderServer) InjectServerCertificatesIntoDefaultDialer() { + certs := x509.NewCertPool() + for _, c := range m.server.TLS.Certificates { + roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) + if err != nil { + panic(fmt.Errorf("error parsing server's root cert: %v", err)) + } + for _, root := range roots { + certs.AddCert(root) + } + } + + testDialer := websocket.Dialer{ + Subprotocols: []string{"p1", "p2"}, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + } + testDialer.TLSClientConfig = &tls.Config{ + RootCAs: certs, + MinVersion: tls.VersionTLS12, + } + websocket.DefaultDialer = &testDialer +} + +var upgrader = websocket.Upgrader{} + +func echo(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + defer c.Close() + for { + mt, message, err := c.ReadMessage() + if err != nil { + break + } + err = c.WriteMessage(mt, message) + if err != nil { + break + } + } +} diff --git a/oracle/price-feeder/oracle/provider/mock_server_test.go b/oracle/price-feeder/oracle/provider/mock_server_test.go new file mode 100644 index 0000000000..a0231db860 --- /dev/null +++ b/oracle/price-feeder/oracle/provider/mock_server_test.go @@ -0,0 +1,34 @@ +package provider + +import ( + "testing" + + "github.com/gorilla/websocket" +) + +func TestMockServer(t *testing.T) { + s := NewMockProviderServer() + s.Start() + defer s.Close() + + // Connect to the server + ws, _, err := websocket.DefaultDialer.Dial(s.GetWebsocketURL(), nil) + if err != nil { + t.Fatalf("%v", err) + } + defer ws.Close() + + // Send message to server, read response and check to see if it's what we expect. + for i := 0; i < 10; i++ { + if err := ws.WriteMessage(websocket.TextMessage, []byte("hello")); err != nil { + t.Fatalf("%v", err) + } + _, p, err := ws.ReadMessage() + if err != nil { + t.Fatalf("%v", err) + } + if string(p) != "hello" { + t.Fatalf("bad message") + } + } +} diff --git a/oracle/price-feeder/oracle/provider/osmosis.go b/oracle/price-feeder/oracle/provider/osmosis.go deleted file mode 100644 index f48fcfe66f..0000000000 --- a/oracle/price-feeder/oracle/provider/osmosis.go +++ /dev/null @@ -1,213 +0,0 @@ -package provider - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - "strings" - - "github.com/sei-protocol/sei-chain/oracle/price-feeder/config" - "github.com/sei-protocol/sei-chain/oracle/price-feeder/oracle/types" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -const ( - osmosisRestURL = "https://api-osmosis.imperator.co" - osmosisTokenEndpoint = "/tokens/v2" - osmosisCandleEndpoint = "/tokens/v2/historical" - osmosisPairsEndpoint = "/pairs/v1/summary" -) - -var _ Provider = (*OsmosisProvider)(nil) - -type ( - // OsmosisProvider defines an Oracle provider implemented by the Osmosis public - // API. - // - // REF: https://api-osmosis.imperator.co/swagger/ - OsmosisProvider struct { - baseURL string - client *http.Client - } - - // OsmosisTokenResponse defines the response structure for an Osmosis token - // request. - OsmosisTokenResponse struct { - Price float64 `json:"price"` - Symbol string `json:"symbol"` - Volume float64 `json:"volume_24h"` - } - - // OsmosisCandleResponse defines the response structure for an Osmosis candle - // request. - OsmosisCandleResponse struct { - Time int64 `json:"time"` - Close float64 `json:"close"` - Volume float64 `json:"volume"` - } - - // OsmosisPairsSummary defines the response structure for an Osmosis pairs - // summary. - OsmosisPairsSummary struct { - Data []OsmosisPairData `json:"data"` - } - - // OsmosisPairData defines the data response structure for an Osmosis pair. - OsmosisPairData struct { - Base string `json:"base_symbol"` - Quote string `json:"quote_symbol"` - } -) - -func NewOsmosisProvider(endpoint config.ProviderEndpoint) *OsmosisProvider { - if endpoint.Name == config.ProviderOsmosis { - return &OsmosisProvider{ - baseURL: endpoint.Rest, - client: newDefaultHTTPClient(), - } - } - return &OsmosisProvider{ - baseURL: osmosisRestURL, - client: newDefaultHTTPClient(), - } -} - -func (p OsmosisProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]TickerPrice, error) { - path := fmt.Sprintf("%s%s/all", p.baseURL, osmosisTokenEndpoint) - - resp, err := p.client.Get(path) - if err != nil { - return nil, fmt.Errorf("failed to make Osmosis request: %w", err) - } - - defer resp.Body.Close() - - bz, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis response body: %w", err) - } - - var tokensResp []OsmosisTokenResponse - if err := json.Unmarshal(bz, &tokensResp); err != nil { - return nil, fmt.Errorf("failed to unmarshal Osmosis response body: %w", err) - } - - baseDenomIdx := make(map[string]types.CurrencyPair) - for _, cp := range pairs { - baseDenomIdx[strings.ToUpper(cp.Base)] = cp - } - - tickerPrices := make(map[string]TickerPrice, len(pairs)) - for _, tr := range tokensResp { - symbol := strings.ToUpper(tr.Symbol) // symbol == base in a currency pair - - cp, ok := baseDenomIdx[symbol] - if !ok { - // skip tokens that are not requested - continue - } - - if _, ok := tickerPrices[symbol]; ok { - return nil, fmt.Errorf("duplicate token found in Osmosis response: %s", symbol) - } - - priceRaw := strconv.FormatFloat(tr.Price, 'f', -1, 64) - price, err := sdk.NewDecFromStr(priceRaw) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis price (%s) for %s", priceRaw, symbol) - } - - volumeRaw := strconv.FormatFloat(tr.Volume, 'f', -1, 64) - volume, err := sdk.NewDecFromStr(volumeRaw) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis volume (%s) for %s", volumeRaw, symbol) - } - - tickerPrices[cp.String()] = TickerPrice{Price: price, Volume: volume} - } - - for _, cp := range pairs { - if _, ok := tickerPrices[cp.String()]; !ok { - return nil, fmt.Errorf("missing exchange rate for %s", cp.String()) - } - } - - return tickerPrices, nil -} - -func (p OsmosisProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]CandlePrice, error) { - candles := make(map[string][]CandlePrice) - for _, pair := range pairs { - if _, ok := candles[pair.Base]; !ok { - candles[pair.String()] = []CandlePrice{} - } - - path := fmt.Sprintf("%s%s/%s/chart?tf=5", p.baseURL, osmosisCandleEndpoint, pair.Base) - - resp, err := p.client.Get(path) - if err != nil { - return nil, fmt.Errorf("failed to make Osmosis request: %w", err) - } - - defer resp.Body.Close() - - bz, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis response body: %w", err) - } - - var candlesResp []OsmosisCandleResponse - if err := json.Unmarshal(bz, &candlesResp); err != nil { - return nil, fmt.Errorf("failed to unmarshal Osmosis response body: %w", err) - } - - candlePrices := []CandlePrice{} - for _, responseCandle := range candlesResp { - closeStr := fmt.Sprintf("%f", responseCandle.Close) - volumeStr := fmt.Sprintf("%f", responseCandle.Volume) - candlePrices = append(candlePrices, CandlePrice{ - Price: sdk.MustNewDecFromStr(closeStr), - Volume: sdk.MustNewDecFromStr(volumeStr), - TimeStamp: responseCandle.Time, - }) - } - candles[pair.String()] = candlePrices - } - - return candles, nil -} - -// GetAvailablePairs return all available pairs symbol to susbscribe. -func (p OsmosisProvider) GetAvailablePairs() (map[string]struct{}, error) { - path := fmt.Sprintf("%s%s", p.baseURL, osmosisPairsEndpoint) - - resp, err := p.client.Get(path) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary OsmosisPairsSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Data)) - for _, pair := range pairsSummary.Data { - cp := types.CurrencyPair{ - Base: strings.ToUpper(pair.Base), - Quote: strings.ToUpper(pair.Quote), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -// SubscribeCurrencyPairs performs a no-op since osmosis does not use websockets -func (p OsmosisProvider) SubscribeCurrencyPairs(pairs ...types.CurrencyPair) error { - return nil -} diff --git a/oracle/price-feeder/oracle/provider/osmosis_test.go b/oracle/price-feeder/oracle/provider/osmosis_test.go deleted file mode 100644 index 87b5e865fe..0000000000 --- a/oracle/price-feeder/oracle/provider/osmosis_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package provider - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/sei-protocol/sei-chain/oracle/price-feeder/config" - "github.com/sei-protocol/sei-chain/oracle/price-feeder/oracle/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestOsmosisProvider_GetTickerPrices(t *testing.T) { - p := NewOsmosisProvider(config.ProviderEndpoint{}) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - resp := `[ - { - "price": 100.22, - "denom": "ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", - "symbol": "LUNA", - "liquidity": 56928301.60178607, - "volume_24h": 7047660.837452592, - "name": "Luna" - }, - { - "price": 28.52, - "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - "symbol": "ATOM", - "liquidity": 189672157.83693966, - "volume_24h": 17006018.613512218, - "name": "Cosmos" - } - ] - ` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr("28.52"), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("17006018.613512218"), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - resp := `[ - { - "price": 100.22, - "denom": "ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", - "symbol": "LUNA", - "liquidity": 56928301.60178607, - "volume_24h": 7047660.837452592, - "name": "Luna" - }, - { - "price": 28.52, - "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - "symbol": "ATOM", - "liquidity": 189672157.83693966, - "volume_24h": 17006018.613512218, - "name": "Cosmos" - } - ] - ` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr("28.52"), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("17006018.613512218"), prices["ATOMUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr("100.22"), prices["LUNAUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("7047660.837452592"), prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_bad_response", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - rw.Write([]byte(`FOO`)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.Error(t, err) - require.Nil(t, prices) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - resp := `[ - { - "price": 100.22, - "denom": "ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", - "symbol": "LUNA", - "liquidity": 56928301.60178607, - "volume_24h": 7047660.837452592, - "name": "Luna" - }, - { - "price": 28.52, - "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - "symbol": "ATOM", - "liquidity": 189672157.83693966, - "volume_24h": 17006018.613512218, - "name": "Cosmos" - } - ] - ` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.Error(t, err) - require.Nil(t, prices) - }) - - t.Run("check_redirect", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - http.Redirect(rw, r, p.baseURL, http.StatusTemporaryRedirect) - })) - defer server.Close() - - server.Client().CheckRedirect = preventRedirect - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.Error(t, err) - require.Nil(t, prices) - }) -} - -func TestOsmosisProvider_GetAvailablePairs(t *testing.T) { - p := NewOsmosisProvider(config.ProviderEndpoint{}) - p.GetAvailablePairs() - - t.Run("valid_available_pair", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/pairs/v1/summary", req.URL.String()) - resp := `{ - "data": [ - { - "base_symbol": "ATOM", - "quote_symbol": "OSMO" - }, - { - "base_symbol": "ION", - "quote_symbol": "OSMO" - } - ] - }` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - availablePairs, err := p.GetAvailablePairs() - require.Nil(t, err) - - _, exist := availablePairs["ATOMOSMO"] - require.True(t, exist) - - _, exist = availablePairs["IONOSMO"] - require.True(t, exist) - }) -} diff --git a/oracle/price-feeder/oracle/provider/provider.go b/oracle/price-feeder/oracle/provider/provider.go index 784fbf0b98..9e80774c1b 100644 --- a/oracle/price-feeder/oracle/provider/provider.go +++ b/oracle/price-feeder/oracle/provider/provider.go @@ -62,14 +62,18 @@ type AggregatedProviderCandles map[string]map[string][]CandlePrice // preventRedirect avoid any redirect in the http.Client the request call // will not return an error, but a valid response with redirect response code. +// +//nolint:unused func preventRedirect(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse } +//nolint:unused,deadcode func newDefaultHTTPClient() *http.Client { return newHTTPClientWithTimeout(defaultTimeout) } +//nolint:unused func newHTTPClientWithTimeout(timeout time.Duration) *http.Client { return &http.Client{ Timeout: timeout, @@ -111,6 +115,7 @@ func PastUnixTime(t time.Duration) int64 { return time.Now().Add(t*-1).Unix() * int64(time.Second/time.Millisecond) } +//nolint:unused,deadcode func strToDec(str string) sdk.Dec { if strings.Contains(str, ".") { split := strings.Split(str, ".") diff --git a/oracle/price-feeder/oracle/util_test.go b/oracle/price-feeder/oracle/util_test.go index 5e4d33f228..d7f346e885 100644 --- a/oracle/price-feeder/oracle/util_test.go +++ b/oracle/price-feeder/oracle/util_test.go @@ -123,7 +123,7 @@ func TestStandardDeviation(t *testing.T) { "ATOM": sdk.MustNewDecFromStr("28.23000000"), "UMEE": sdk.MustNewDecFromStr("1.13050000"), }, - config.ProviderOsmosis: { + config.ProviderCoinbase: { "ATOM": sdk.MustNewDecFromStr("28.40000000"), "UMEE": sdk.MustNewDecFromStr("1.14000000"), "LUNA": sdk.MustNewDecFromStr("64.10000000"), @@ -154,7 +154,7 @@ func TestStandardDeviation(t *testing.T) { "UMEE": sdk.MustNewDecFromStr("1.13050000"), "LUNA": sdk.MustNewDecFromStr("64.85000000"), }, - config.ProviderOsmosis: { + config.ProviderCoinbase: { "ATOM": sdk.MustNewDecFromStr("28.40000000"), "UMEE": sdk.MustNewDecFromStr("1.14000000"), "LUNA": sdk.MustNewDecFromStr("64.10000000"), diff --git a/proto/dex/params.proto b/proto/dex/params.proto index 64754c02be..ff44257e54 100644 --- a/proto/dex/params.proto +++ b/proto/dex/params.proto @@ -14,4 +14,9 @@ message Params { (gogoproto.moretags) = "yaml:\"price_snapshot_retention\"", (gogoproto.jsontag) = "price_snapshot_retention" ]; -} \ No newline at end of file + string sudo_call_gas_price = 2 [ + (gogoproto.jsontag) = "sudo_call_gas_price", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/utils/datastructures/typed_sync_map.go b/utils/datastructures/typed_sync_map.go index 5309eb53a5..1dbba07561 100644 --- a/utils/datastructures/typed_sync_map.go +++ b/utils/datastructures/typed_sync_map.go @@ -1,17 +1,20 @@ package datastructures import ( + "sort" "sync" + + "golang.org/x/exp/constraints" ) // A map-like data structure that is guaranteed to be data race free during write // operations. It is a typed wrapper over the builtin typeless `sync.Map`. The // CRUD interface is exactly the same as those of `sync.Map`. -type TypedSyncMap[K any, V any] struct { +type TypedSyncMap[K constraints.Ordered, V any] struct { internal *sync.Map } -func NewTypedSyncMap[K any, V any]() *TypedSyncMap[K, V] { +func NewTypedSyncMap[K constraints.Ordered, V any]() *TypedSyncMap[K, V] { return &TypedSyncMap[K, V]{ internal: &sync.Map{}, } @@ -38,10 +41,21 @@ func (m *TypedSyncMap[K, V]) Delete(key K) { } func (m *TypedSyncMap[K, V]) Range(f func(K, V) bool) { + // All map iterations should be deterministic, so we apply f in sorted order to avoid nondeterminism + var keys []K m.internal.Range(func(key, val any) bool { - typedKey, typedVal := key.(K), val.(V) - return f(typedKey, typedVal) + keys = append(keys, key.(K)) + return true + }) + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] }) + + for _, key := range keys { + val, _ := m.internal.Load(key) + typedVal := val.(V) + f(key, typedVal) + } } func (m *TypedSyncMap[K, V]) Len() int { @@ -75,12 +89,12 @@ func (m *TypedSyncMap[K, V]) DeepApply(toApply func(V)) { // nested values directly. For example, to set value `v` for outer key `k1` and inner // key `k2`, one can simply call StoreNested(k1, k2, v), without worrying about creating // the inner map if it doesn't exist. -type TypedNestedSyncMap[K1 any, K2 any, V any] struct { +type TypedNestedSyncMap[K1 constraints.Ordered, K2 constraints.Ordered, V any] struct { *TypedSyncMap[K1, *TypedSyncMap[K2, V]] mu *sync.Mutex // XXXNested methods have write operations outside sync.Map } -func NewTypedNestedSyncMap[K1 any, K2 any, V any]() *TypedNestedSyncMap[K1, K2, V] { +func NewTypedNestedSyncMap[K1 constraints.Ordered, K2 constraints.Ordered, V any]() *TypedNestedSyncMap[K1, K2, V] { return &TypedNestedSyncMap[K1, K2, V]{ TypedSyncMap: NewTypedSyncMap[K1, *TypedSyncMap[K2, V]](), mu: &sync.Mutex{}, diff --git a/utils/datastructures/typed_sync_map_test.go b/utils/datastructures/typed_sync_map_test.go index 9919b6bb3e..a0c408913a 100644 --- a/utils/datastructures/typed_sync_map_test.go +++ b/utils/datastructures/typed_sync_map_test.go @@ -78,13 +78,20 @@ func TestTypedSyncMapDeepCopy(t *testing.T) { } func TestTypedSyncMapDeepApply(t *testing.T) { - m := datastructures.NewTypedSyncMap[int, int]() - m.Store(1, 1) - m.Store(2, 2) + m := datastructures.NewTypedSyncMap[string, int]() + m.Store("a", 1) + m.Store("c", 3) + m.Store("b", 2) agg := 0 - m.DeepApply(func(i int) { agg += i }) - require.Equal(t, 2, m.Len()) - require.Equal(t, 3, agg) + lastSeen := -1 + m.DeepApply(func(i int) { + agg += i + // Require that entries are applied in sorted order + require.Less(t, lastSeen, i) + lastSeen = i + }) + require.Equal(t, 3, m.Len()) + require.Equal(t, 6, agg) } func TestTypedNestedSyncMapSequantial(t *testing.T) { diff --git a/utils/slice.go b/utils/slice.go index 23bf8126a2..cf2a37cbcc 100644 --- a/utils/slice.go +++ b/utils/slice.go @@ -28,3 +28,13 @@ func Reduce[I, O any](input []I, reducer func(I, O) O, initial O) O { } return initial } + +func Filter[T any](slice []T, lambda func(t T) bool) []T { + res := []T{} + for _, t := range slice { + if lambda(t) { + res = append(res, t) + } + } + return res +} diff --git a/x/dex/cache/order.go b/x/dex/cache/order.go index 967da54f2d..1b3deedde5 100644 --- a/x/dex/cache/order.go +++ b/x/dex/cache/order.go @@ -42,17 +42,12 @@ func (o *BlockOrders) GetSortedMarketOrders(direction types.PositionDirection, i o.mu.Lock() defer o.mu.Unlock() - orderTypes := map[types.OrderType]bool{ - types.OrderType_MARKET: true, - types.OrderType_FOKMARKET: true, - types.OrderType_FOKMARKETBYVALUE: true, - } + res := o.getOrdersByCriteria(types.OrderType_MARKET, direction) + res = append(res, o.getOrdersByCriteria(types.OrderType_FOKMARKET, direction)...) if includeLiquidationOrders { - orderTypes[types.OrderType_LIQUIDATION] = true + res = append(res, o.getOrdersByCriteria(types.OrderType_LIQUIDATION, direction)...) } - res := o.getOrdersByCriteriaMap(orderTypes, map[types.PositionDirection]bool{direction: true}) - - sort.Slice(res, func(i, j int) bool { + sort.SliceStable(res, func(i, j int) bool { // a price of 0 indicates that there is no worst price for the order, so it should // always be ranked at the top. if res[i].Price.IsZero() { diff --git a/x/dex/contract/abci.go b/x/dex/contract/abci.go index bfddb00cd5..235b333f39 100644 --- a/x/dex/contract/abci.go +++ b/x/dex/contract/abci.go @@ -103,7 +103,7 @@ func cacheAndDecorateContext(ctx sdk.Context, env *environment) (sdk.Context, sd cachedCtx, msCached := store.GetCachedContext(ctx) goCtx := context.WithValue(cachedCtx.Context(), dexcache.CtxKeyExecTermSignal, env.executionTerminationSignals) cachedCtx = cachedCtx.WithContext(goCtx) - decoratedCtx := cachedCtx.WithGasMeter(seisync.NewGasWrapper(cachedCtx.GasMeter())).WithBlockGasMeter( + decoratedCtx := cachedCtx.WithGasMeter(seisync.NewGasWrapper(sdk.NewInfiniteGasMeter())).WithBlockGasMeter( seisync.NewGasWrapper(cachedCtx.BlockGasMeter()), ) return decoratedCtx, msCached diff --git a/x/dex/contract/whitelist.go b/x/dex/contract/whitelist.go index 0f70aad100..5e62948014 100644 --- a/x/dex/contract/whitelist.go +++ b/x/dex/contract/whitelist.go @@ -5,6 +5,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/dex/keeper" "github.com/sei-protocol/sei-chain/x/dex/types" ) @@ -19,6 +20,7 @@ var DexWhitelistedKeys = []string{ types.SettlementEntryKey, types.NextOrderIDKey, types.MatchResultKey, + keeper.ContractPrefixKey, } var WasmWhitelistedKeys = []string{ diff --git a/x/dex/keeper/contract.go b/x/dex/keeper/contract.go index 752380aa3b..86862e0c0d 100644 --- a/x/dex/keeper/contract.go +++ b/x/dex/keeper/contract.go @@ -67,6 +67,24 @@ func (k Keeper) GetAllContractInfo(ctx sdk.Context) []types.ContractInfoV2 { return list } +func (k Keeper) ChargeRentForGas(ctx sdk.Context, contractAddr string, gasUsed uint64) error { + contract, err := k.GetContract(ctx, contractAddr) + if err != nil { + return err + } + params := k.GetParams(ctx) + gasPrice := sdk.NewDec(int64(gasUsed)).Mul(params.SudoCallGasPrice).RoundInt().Int64() + if gasPrice > int64(contract.RentBalance) { + contract.RentBalance = 0 + if err := k.SetContract(ctx, &contract); err != nil { + return err + } + return errors.New("insufficient rent") + } + contract.RentBalance -= uint64(gasPrice) + return k.SetContract(ctx, &contract) +} + func contractKey(contractAddr string) []byte { return []byte(contractAddr) } diff --git a/x/dex/keeper/contract_test.go b/x/dex/keeper/contract_test.go new file mode 100644 index 0000000000..2a0d7dae9e --- /dev/null +++ b/x/dex/keeper/contract_test.go @@ -0,0 +1,30 @@ +package keeper_test + +import ( + "testing" + + keepertest "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/dex/types" + "github.com/stretchr/testify/require" +) + +func TestChargeRentForGas(t *testing.T) { + keeper, ctx := keepertest.DexKeeper(t) + err := keeper.SetContract(ctx, &types.ContractInfoV2{ + Creator: keepertest.TestAccount, + ContractAddr: keepertest.TestContract, + CodeId: 1, + RentBalance: 1000000, + }) + require.Nil(t, err) + err = keeper.ChargeRentForGas(ctx, keepertest.TestContract, 5000000) + require.Nil(t, err) + contract, err := keeper.GetContract(ctx, keepertest.TestContract) + require.Nil(t, err) + require.Equal(t, uint64(500000), contract.RentBalance) + err = keeper.ChargeRentForGas(ctx, keepertest.TestContract, 6000000) + require.NotNil(t, err) + contract, err = keeper.GetContract(ctx, keepertest.TestContract) + require.Nil(t, err) + require.Equal(t, uint64(0), contract.RentBalance) +} diff --git a/x/dex/keeper/match_result.go b/x/dex/keeper/match_result.go index 12fe1032d1..ca15ef12eb 100644 --- a/x/dex/keeper/match_result.go +++ b/x/dex/keeper/match_result.go @@ -1,13 +1,13 @@ package keeper import ( - "encoding/binary" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/x/dex/types" ) +const MatchResultKey = "match-result" + func (k Keeper) SetMatchResult(ctx sdk.Context, contractAddr string, result *types.MatchResult) { store := prefix.NewStore( ctx.KVStore(k.storeKey), @@ -20,22 +20,15 @@ func (k Keeper) SetMatchResult(ctx sdk.Context, contractAddr string, result *typ if err != nil { panic(err) } - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, uint64(height)) - store.Set(key, bz) + store.Set([]byte(MatchResultKey), bz) } -func (k Keeper) GetMatchResultState(ctx sdk.Context, contractAddr string, height int64) (*types.MatchResult, bool) { +func (k Keeper) GetMatchResultState(ctx sdk.Context, contractAddr string) (*types.MatchResult, bool) { store := prefix.NewStore( ctx.KVStore(k.storeKey), types.MatchResultPrefix(contractAddr), ) - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, uint64(height)) - if !store.Has(key) { - return nil, false - } - bz := store.Get(key) + bz := store.Get([]byte(MatchResultKey)) result := types.MatchResult{} if err := result.Unmarshal(bz); err != nil { panic(err) diff --git a/x/dex/keeper/query/grpc_query_match_result.go b/x/dex/keeper/query/grpc_query_match_result.go index 0a75329ab5..95226904e6 100644 --- a/x/dex/keeper/query/grpc_query_match_result.go +++ b/x/dex/keeper/query/grpc_query_match_result.go @@ -15,7 +15,7 @@ func (k KeeperWrapper) GetMatchResult(c context.Context, req *types.QueryGetMatc } ctx := sdk.UnwrapSDKContext(c) - result, found := k.GetMatchResultState(ctx, req.ContractAddr, req.Height) + result, found := k.GetMatchResultState(ctx, req.ContractAddr) if !found { return nil, status.Error(codes.NotFound, "result not found") } diff --git a/x/dex/keeper/utils/order_book.go b/x/dex/keeper/utils/order_book.go index e28fd00c48..5c5578d3d6 100644 --- a/x/dex/keeper/utils/order_book.go +++ b/x/dex/keeper/utils/order_book.go @@ -33,7 +33,7 @@ func PopulateOrderbook( } func sortOrderBookEntries(entries []types.OrderBookEntry) { - sort.Slice(entries, func(i, j int) bool { + sort.SliceStable(entries, func(i, j int) bool { return entries[i].GetPrice().LT(entries[j].GetPrice()) }) } diff --git a/x/dex/keeper/utils/wasm.go b/x/dex/keeper/utils/wasm.go index 82d1556699..c5127120f3 100644 --- a/x/dex/keeper/utils/wasm.go +++ b/x/dex/keeper/utils/wasm.go @@ -35,16 +35,18 @@ func getMsgType(msg interface{}) string { } } -func sudo(sdkCtx sdk.Context, k *keeper.Keeper, contractAddress []byte, wasmMsg []byte, msgType string) ([]byte, error) { +func sudo(sdkCtx sdk.Context, k *keeper.Keeper, contractAddress []byte, wasmMsg []byte, msgType string) ([]byte, uint64, error) { // Measure the time it takes to execute the contract in WASM defer metrics.MeasureSudoExecutionDuration(time.Now(), msgType) + gasConsumedBefore := sdkCtx.GasMeter().GasConsumed() data, err := k.WasmKeeper.Sudo( sdkCtx, contractAddress, wasmMsg, ) + gasConsumedAfter := sdkCtx.GasMeter().GasConsumed() if hasErrInstantiatingWasmModuleDueToCPUFeature(err) { panic(utils.DecorateHardFailError(err)) } - return data, err + return data, gasConsumedAfter - gasConsumedBefore, err } func hasErrInstantiatingWasmModuleDueToCPUFeature(err error) bool { @@ -66,11 +68,16 @@ func CallContractSudo(sdkCtx sdk.Context, k *keeper.Keeper, contractAddr string, return []byte{}, err } msgType := getMsgType(msg) - data, err := sudo(sdkCtx, k, contractAddress, wasmMsg, msgType) + data, gasUsed, err := sudo(sdkCtx, k, contractAddress, wasmMsg, msgType) if err != nil { metrics.IncrementSudoFailCount(msgType) sdkCtx.Logger().Error(err.Error()) return []byte{}, err } + if err := k.ChargeRentForGas(sdkCtx, contractAddr, gasUsed); err != nil { + metrics.IncrementSudoFailCount(msgType) + sdkCtx.Logger().Error(err.Error()) + return []byte{}, err + } return data, nil } diff --git a/x/dex/migrations/v8_to_v9.go b/x/dex/migrations/v8_to_v9.go index 493069c222..cb0e6095d3 100644 --- a/x/dex/migrations/v8_to_v9.go +++ b/x/dex/migrations/v8_to_v9.go @@ -33,5 +33,6 @@ func V8ToV9(ctx sdk.Context, dexkeeper keeper.Keeper) error { } contractStore.Set(iterator.Key(), bz) } + return nil } diff --git a/x/dex/module.go b/x/dex/module.go index 586d87e450..acee7de30f 100644 --- a/x/dex/module.go +++ b/x/dex/module.go @@ -211,7 +211,8 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 9 } func (am AppModule) getAllContractInfo(ctx sdk.Context) []types.ContractInfoV2 { - return am.keeper.GetAllContractInfo(ctx) + // Do not process any contract that has a non-zero rent balance + return utils.Filter(am.keeper.GetAllContractInfo(ctx), func(c types.ContractInfoV2) bool { return c.RentBalance > 0 }) } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. diff --git a/x/dex/module_test.go b/x/dex/module_test.go index b96402e574..125fac6ae2 100644 --- a/x/dex/module_test.go +++ b/x/dex/module_test.go @@ -74,7 +74,7 @@ func TestEndBlockMarketOrder(t *testing.T) { if err != nil { panic(err) } - dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true}) + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true, RentBalance: 100000000}) dexkeeper.AddRegisteredPair(ctx, contractAddr.String(), pair) dexutils.GetMemState(ctx.Context()).GetBlockOrders(ctx, utils.ContractAddress(contractAddr.String()), utils.GetPairString(&pair)).Add( &types.Order{ @@ -145,7 +145,7 @@ func TestEndBlockMarketOrder(t *testing.T) { _, found = dexkeeper.GetLongBookByPrice(ctx, contractAddr.String(), sdk.MustNewDecFromStr("1"), pair.PriceDenom, pair.AssetDenom) require.True(t, found) - matchResults, _ := dexkeeper.GetMatchResultState(ctx, contractAddr.String(), 2) + matchResults, _ := dexkeeper.GetMatchResultState(ctx, contractAddr.String()) require.Equal(t, 1, len(matchResults.Orders)) require.Equal(t, 2, len(matchResults.Settlements)) @@ -168,7 +168,7 @@ func TestEndBlockMarketOrder(t *testing.T) { ctx = ctx.WithBlockHeight(3) testApp.EndBlocker(ctx, abci.RequestEndBlock{}) - matchResults, _ = dexkeeper.GetMatchResultState(ctx, contractAddr.String(), 3) + matchResults, _ = dexkeeper.GetMatchResultState(ctx, contractAddr.String()) require.Equal(t, 1, len(matchResults.Orders)) require.Equal(t, 0, len(matchResults.Settlements)) } @@ -202,7 +202,7 @@ func TestEndBlockLimitOrder(t *testing.T) { panic(err) } - dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true}) + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true, RentBalance: 100000000}) dexkeeper.AddRegisteredPair(ctx, contractAddr.String(), pair) dexutils.GetMemState(ctx.Context()).GetBlockOrders(ctx, utils.ContractAddress(contractAddr.String()), utils.GetPairString(&pair)).Add( &types.Order{ @@ -305,7 +305,7 @@ func TestEndBlockLimitOrder(t *testing.T) { _, found = dexkeeper.GetLongBookByPrice(ctx, contractAddr.String(), sdk.MustNewDecFromStr("3"), pair.PriceDenom, pair.AssetDenom) require.False(t, found) - matchResults, _ := dexkeeper.GetMatchResultState(ctx, contractAddr.String(), 2) + matchResults, _ := dexkeeper.GetMatchResultState(ctx, contractAddr.String()) require.Equal(t, 2, len(matchResults.Orders)) require.Equal(t, 4, len(matchResults.Settlements)) @@ -335,7 +335,7 @@ func TestEndBlockLimitOrder(t *testing.T) { _, found = dexkeeper.GetShortBookByPrice(ctx, contractAddr.String(), sdk.MustNewDecFromStr("3"), pair.PriceDenom, pair.AssetDenom) require.False(t, found) - matchResults, _ = dexkeeper.GetMatchResultState(ctx, contractAddr.String(), 3) + matchResults, _ = dexkeeper.GetMatchResultState(ctx, contractAddr.String()) require.Equal(t, 1, len(matchResults.Orders)) require.Equal(t, 2, len(matchResults.Settlements)) } @@ -347,7 +347,7 @@ func TestEndBlockRollback(t *testing.T) { dexkeeper := testApp.DexKeeper pair := TEST_PAIR() // register contract and pair - dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: keepertest.TestContract, NeedHook: false, NeedOrderMatching: true}) + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: keepertest.TestContract, NeedHook: false, NeedOrderMatching: true, RentBalance: 100000000}) dexkeeper.AddRegisteredPair(ctx, keepertest.TestContract, pair) // place one order to a nonexistent contract dexutils.GetMemState(ctx.Context()).GetBlockOrders(ctx, utils.ContractAddress(keepertest.TestContract), utils.GetPairString(&pair)).Add( @@ -366,8 +366,8 @@ func TestEndBlockRollback(t *testing.T) { ctx = ctx.WithBlockHeight(1) testApp.EndBlocker(ctx, abci.RequestEndBlock{}) // No state change should've been persisted - _, found := dexkeeper.GetMatchResultState(ctx, keepertest.TestContract, 2) - require.False(t, found) + matchResult, _ := dexkeeper.GetMatchResultState(ctx, keepertest.TestContract) + require.Equal(t, &types.MatchResult{}, matchResult) } func TestEndBlockPartialRollback(t *testing.T) { @@ -378,7 +378,7 @@ func TestEndBlockPartialRollback(t *testing.T) { dexkeeper := testApp.DexKeeper pair := TEST_PAIR() // register contract and pair - dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: keepertest.TestContract, NeedHook: false, NeedOrderMatching: true}) + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: keepertest.TestContract, NeedHook: false, NeedOrderMatching: true, RentBalance: 100000000}) dexkeeper.AddRegisteredPair(ctx, keepertest.TestContract, pair) // place one order to a nonexistent contract dexutils.GetMemState(ctx.Context()).GetBlockOrders(ctx, utils.ContractAddress(keepertest.TestContract), utils.GetPairString(&pair)).Add( @@ -416,7 +416,7 @@ func TestEndBlockPartialRollback(t *testing.T) { if err != nil { panic(err) } - dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true}) + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true, RentBalance: 100000000}) dexkeeper.AddRegisteredPair(ctx, contractAddr.String(), pair) // place one order to a nonexistent contract dexutils.GetMemState(ctx.Context()).GetBlockOrders(ctx, utils.ContractAddress(contractAddr.String()), utils.GetPairString(&pair)).Add( @@ -444,12 +444,12 @@ func TestEndBlockPartialRollback(t *testing.T) { ctx = ctx.WithBlockHeight(1) testApp.EndBlocker(ctx, abci.RequestEndBlock{}) // No state change should've been persisted for bad contract - _, found := dexkeeper.GetMatchResultState(ctx, keepertest.TestContract, 1) - require.False(t, found) + matchResult, _ := dexkeeper.GetMatchResultState(ctx, keepertest.TestContract) + require.Equal(t, &types.MatchResult{}, matchResult) // state change should've been persisted for good contract - matchResult, _ := dexkeeper.GetMatchResultState(ctx, contractAddr.String(), 1) + matchResult, _ = dexkeeper.GetMatchResultState(ctx, contractAddr.String()) require.Equal(t, 1, len(matchResult.Orders)) - _, found = dexkeeper.GetLongBookByPrice(ctx, contractAddr.String(), sdk.MustNewDecFromStr("0.0001"), pair.PriceDenom, pair.AssetDenom) + _, found := dexkeeper.GetLongBookByPrice(ctx, contractAddr.String(), sdk.MustNewDecFromStr("0.0001"), pair.PriceDenom, pair.AssetDenom) require.True(t, found) } @@ -480,7 +480,7 @@ func TestBeginBlock(t *testing.T) { if err != nil { panic(err) } - dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true}) + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true, RentBalance: 100000000}) // right now just make sure it doesn't crash since it doesn't register any state to be checked against testApp.BeginBlocker(ctx, abci.RequestBeginBlock{}) @@ -520,7 +520,7 @@ func TestEndBlockPanicHandling(t *testing.T) { if err != nil { panic(err) } - dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true}) + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true, RentBalance: 100000000}) dexkeeper.AddRegisteredPair(ctx, contractAddr.String(), pair) dexutils.GetMemState(ctx.Context()).GetBlockOrders(ctx, utils.ContractAddress(contractAddr.String()), utils.GetPairString(&pair)).Add( &types.Order{ diff --git a/x/dex/types/match_result.go b/x/dex/types/match_result.go index 223a1b48f8..d77c3767d4 100644 --- a/x/dex/types/match_result.go +++ b/x/dex/types/match_result.go @@ -9,24 +9,16 @@ func NewMatchResult( cancellations []*Cancellation, settlements []*SettlementEntry, ) *MatchResult { - sort.Slice(orders, func(i, j int) bool { - if i != j && orders[i].Id == orders[j].Id { - panic("orders have identical IDs") - } - return orders[i].Id < orders[j].Id + // Note that we use string comparator since it is more robust. E.g. in the case that 2 orders match + // on the same orderId, we will then sort on the next field + sort.SliceStable(orders, func(i, j int) bool { + return orders[i].String() < orders[j].String() }) - sort.Slice(cancellations, func(i, j int) bool { - if i != j && cancellations[i].Id == cancellations[j].Id { - panic("cancnellations have identical IDs") - } - return cancellations[i].Id < cancellations[j].Id + sort.SliceStable(cancellations, func(i, j int) bool { + return cancellations[i].String() < cancellations[j].String() }) sort.SliceStable(settlements, func(i, j int) bool { - // settlements for the same order ID are always populated - // by the same goroutine, so the ordering among those - // settlements are already deterministic as long as the - // sorting is stable. - return settlements[i].OrderId < settlements[j].OrderId + return settlements[i].String() < settlements[j].String() }) return &MatchResult{ Orders: orders, diff --git a/x/dex/types/match_result_test.go b/x/dex/types/match_result_test.go new file mode 100644 index 0000000000..79da33ec30 --- /dev/null +++ b/x/dex/types/match_result_test.go @@ -0,0 +1,54 @@ +package types_test + +import ( + "testing" + + "github.com/sei-protocol/sei-chain/x/dex/types" + "github.com/stretchr/testify/require" +) + +func TestMatchResultSorted(t *testing.T) { + // Test stable sorting + order1 := &types.Order{ + Id: 1, + } + order2 := &types.Order{ + Id: 1, + } + order3 := &types.Order{ + Id: 2, + } + + // Test sort on different field + cancellation1 := &types.Cancellation{ + Id: 1, + AssetDenom: "a", + } + cancellation2 := &types.Cancellation{ + Id: 1, + AssetDenom: "b", + } + + // Test sort on string + settlement1 := &types.SettlementEntry{ + Account: "a", + } + settlement2 := &types.SettlementEntry{ + Account: "b", + } + + orders := []*types.Order{order3, order1, order2} + cancellations := []*types.Cancellation{cancellation2, cancellation1} + settlements := []*types.SettlementEntry{settlement2, settlement1} + + matchResult := types.NewMatchResult(orders, cancellations, settlements) + expectedOrders := []*types.Order{order1, order2, order3} + expectedCancellations := []*types.Cancellation{cancellation1, cancellation2} + expectedSettlements := []*types.SettlementEntry{settlement1, settlement2} + require.Equal(t, expectedOrders, matchResult.Orders) + // Check actual elements, since slice match above don't seem to capture if the orders point to different objects + require.True(t, order1 == matchResult.Orders[0]) + require.True(t, order2 == matchResult.Orders[1]) + require.Equal(t, expectedCancellations, matchResult.Cancellations) + require.Equal(t, expectedSettlements, matchResult.Settlements) +} diff --git a/x/dex/types/params.go b/x/dex/types/params.go index 20aa00d193..3fb55143ce 100644 --- a/x/dex/types/params.go +++ b/x/dex/types/params.go @@ -3,16 +3,22 @@ package types import ( fmt "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) -var KeyPriceSnapshotRetention = []byte("PriceSnapshotRetention") // number of epochs to retain price snapshots for +var ( + KeyPriceSnapshotRetention = []byte("PriceSnapshotRetention") // number of epochs to retain price snapshots for + KeySudoCallGasPrice = []byte("KeySudoCallGasPrice") // gas price for sudo calls from endblock +) const ( DefaultPriceSnapshotRetention = 24 * 3600 // default to one day ) +var DefaultSudoCallGasPrice = sdk.NewDecWithPrec(1, 1) // 0.1 + var _ paramtypes.ParamSet = (*Params)(nil) // ParamKeyTable the param key table for launch module @@ -29,6 +35,7 @@ func NewParams() Params { func DefaultParams() Params { return Params{ PriceSnapshotRetention: DefaultPriceSnapshotRetention, + SudoCallGasPrice: DefaultSudoCallGasPrice, } } @@ -36,6 +43,7 @@ func DefaultParams() Params { func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{ paramtypes.NewParamSetPair(KeyPriceSnapshotRetention, &p.PriceSnapshotRetention, validatePriceSnapshotRetention), + paramtypes.NewParamSetPair(KeySudoCallGasPrice, &p.SudoCallGasPrice, validateSudoCallGasPrice), } } @@ -62,3 +70,7 @@ func validatePriceSnapshotRetention(i interface{}) error { return nil } + +func validateSudoCallGasPrice(i interface{}) error { + return nil +} diff --git a/x/dex/types/params.pb.go b/x/dex/types/params.pb.go index d10563e263..35befdc34a 100644 --- a/x/dex/types/params.pb.go +++ b/x/dex/types/params.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" io "io" @@ -25,7 +26,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { - PriceSnapshotRetention uint64 `protobuf:"varint,1,opt,name=price_snapshot_retention,json=priceSnapshotRetention,proto3" json:"price_snapshot_retention" yaml:"price_snapshot_retention"` + PriceSnapshotRetention uint64 `protobuf:"varint,1,opt,name=price_snapshot_retention,json=priceSnapshotRetention,proto3" json:"price_snapshot_retention" yaml:"price_snapshot_retention"` + SudoCallGasPrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=sudo_call_gas_price,json=sudoCallGasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"sudo_call_gas_price"` } func (m *Params) Reset() { *m = Params{} } @@ -74,22 +76,26 @@ func init() { func init() { proto.RegisterFile("dex/params.proto", fileDescriptor_e49286500ccff43e) } var fileDescriptor_e49286500ccff43e = []byte{ - // 227 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x49, 0xad, 0xd0, - 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x28, 0x4e, - 0xcd, 0x04, 0xb3, 0x92, 0xf3, 0x73, 0xf4, 0x8a, 0x53, 0x33, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, - 0x52, 0x52, 0x2b, 0xa4, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x52, 0xfa, 0x20, 0x16, 0x44, 0xbd, - 0x52, 0x2f, 0x23, 0x17, 0x5b, 0x00, 0xd8, 0x00, 0xa1, 0x4a, 0x2e, 0x89, 0x82, 0xa2, 0xcc, 0xe4, - 0xd4, 0xf8, 0xe2, 0xbc, 0xc4, 0x82, 0xe2, 0x8c, 0xfc, 0x92, 0xf8, 0xa2, 0xd4, 0x92, 0xd4, 0xbc, - 0x92, 0xcc, 0xfc, 0x3c, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x16, 0x27, 0xfb, 0x57, 0xf7, 0xe4, 0x71, - 0xaa, 0xf9, 0x74, 0x4f, 0x5e, 0xbe, 0x32, 0x31, 0x37, 0xc7, 0x4a, 0x09, 0x97, 0x0a, 0xa5, 0x20, - 0x31, 0xb0, 0x54, 0x30, 0x54, 0x26, 0x08, 0x26, 0x61, 0xc5, 0x31, 0x63, 0x81, 0x3c, 0xc3, 0x8b, - 0x05, 0xf2, 0x8c, 0x4e, 0xee, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, - 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, - 0x9b, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x5f, 0x9c, 0x9a, 0xa9, 0x0b, - 0xf3, 0x25, 0x98, 0x03, 0xf6, 0xa6, 0x7e, 0x85, 0x3e, 0x28, 0x3c, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, - 0x93, 0xd8, 0xc0, 0xf2, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x99, 0x2a, 0x03, 0x6c, 0x23, - 0x01, 0x00, 0x00, + // 303 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x31, 0x4b, 0xc3, 0x40, + 0x14, 0xc7, 0x73, 0x45, 0x8a, 0x66, 0x2a, 0x55, 0x24, 0x38, 0xdc, 0x95, 0x0e, 0xd2, 0xa5, 0xb9, + 0xc1, 0xad, 0x8b, 0x50, 0x95, 0xae, 0xa5, 0x6e, 0x2e, 0xe1, 0x7a, 0x39, 0x92, 0xc3, 0x4b, 0x2e, + 0xe4, 0x5d, 0x21, 0x99, 0xfd, 0x02, 0x8e, 0x8e, 0xfd, 0x38, 0x1d, 0x3b, 0x8a, 0x43, 0x90, 0x64, + 0x91, 0x8e, 0x7e, 0x02, 0xc9, 0xd9, 0x82, 0x83, 0x9d, 0xee, 0xdd, 0xfb, 0xfd, 0x78, 0x0f, 0xfe, + 0xcf, 0xed, 0x85, 0xa2, 0xa0, 0x19, 0xcb, 0x59, 0x02, 0x7e, 0x96, 0x6b, 0xa3, 0xfb, 0x1e, 0x08, + 0x69, 0x2b, 0xae, 0x95, 0x0f, 0x42, 0xf2, 0x98, 0xc9, 0xd4, 0x0f, 0x45, 0x71, 0x75, 0x11, 0xe9, + 0x48, 0x5b, 0x44, 0xdb, 0xea, 0xd7, 0x1f, 0xbe, 0x74, 0xdc, 0xee, 0xdc, 0x0e, 0xe8, 0x97, 0xae, + 0x97, 0xe5, 0x92, 0x8b, 0x00, 0x52, 0x96, 0x41, 0xac, 0x4d, 0x90, 0x0b, 0x23, 0x52, 0x23, 0x75, + 0xea, 0xa1, 0x01, 0x1a, 0x9d, 0x4c, 0x6f, 0x77, 0x15, 0x39, 0xea, 0x7c, 0x57, 0x84, 0x94, 0x2c, + 0x51, 0x93, 0xe1, 0x31, 0x63, 0xb8, 0xb8, 0xb4, 0xe8, 0x71, 0x4f, 0x16, 0x07, 0xd0, 0x37, 0xee, + 0x39, 0xac, 0x42, 0x1d, 0x70, 0xa6, 0x54, 0x10, 0x31, 0x08, 0xac, 0xe7, 0x75, 0x06, 0x68, 0x74, + 0x36, 0x7d, 0xd8, 0x54, 0xc4, 0xf9, 0xa8, 0xc8, 0x75, 0x24, 0x4d, 0xbc, 0x5a, 0xfa, 0x5c, 0x27, + 0x94, 0x6b, 0x48, 0x34, 0xec, 0x9f, 0x31, 0x84, 0xcf, 0xd4, 0x94, 0x99, 0x00, 0xff, 0x5e, 0xf0, + 0x5d, 0x45, 0xfe, 0x1b, 0xb6, 0xe8, 0xb5, 0xcd, 0x3b, 0xa6, 0xd4, 0x8c, 0xc1, 0xbc, 0xed, 0x4c, + 0x4e, 0xdf, 0xd6, 0xc4, 0xf9, 0x5a, 0x13, 0x34, 0x9d, 0x6d, 0x6a, 0x8c, 0xb6, 0x35, 0x46, 0x9f, + 0x35, 0x46, 0xaf, 0x0d, 0x76, 0xb6, 0x0d, 0x76, 0xde, 0x1b, 0xec, 0x3c, 0x8d, 0xff, 0x2c, 0x05, + 0x21, 0xc7, 0x87, 0x6c, 0xed, 0xc7, 0x86, 0x4b, 0x0b, 0xda, 0x5e, 0xc1, 0xee, 0x5f, 0x76, 0x2d, + 0xbf, 0xf9, 0x09, 0x00, 0x00, 0xff, 0xff, 0x19, 0xbd, 0xa8, 0x1c, 0x99, 0x01, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -114,6 +120,9 @@ func (this *Params) Equal(that interface{}) bool { if this.PriceSnapshotRetention != that1.PriceSnapshotRetention { return false } + if !this.SudoCallGasPrice.Equal(that1.SudoCallGasPrice) { + return false + } return true } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -136,6 +145,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.SudoCallGasPrice.Size() + i -= size + if _, err := m.SudoCallGasPrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 if m.PriceSnapshotRetention != 0 { i = encodeVarintParams(dAtA, i, uint64(m.PriceSnapshotRetention)) i-- @@ -164,6 +183,8 @@ func (m *Params) Size() (n int) { if m.PriceSnapshotRetention != 0 { n += 1 + sovParams(uint64(m.PriceSnapshotRetention)) } + l = m.SudoCallGasPrice.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -221,6 +242,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SudoCallGasPrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + 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 ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SudoCallGasPrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/nitro/replay/libnitro_replayer.dylib b/x/nitro/replay/libnitro_replayer.dylib index a92957cbb9..87a1c7708b 100755 Binary files a/x/nitro/replay/libnitro_replayer.dylib and b/x/nitro/replay/libnitro_replayer.dylib differ diff --git a/x/nitro/replay/libnitro_replayer.x86_64.so b/x/nitro/replay/libnitro_replayer.x86_64.so index 70edce1c97..1682c9c473 100755 Binary files a/x/nitro/replay/libnitro_replayer.x86_64.so and b/x/nitro/replay/libnitro_replayer.x86_64.so differ diff --git a/x/nitro/replay/replay.go b/x/nitro/replay/replay.go index 6ecf0e846a..5cb79e239d 100644 --- a/x/nitro/replay/replay.go +++ b/x/nitro/replay/replay.go @@ -15,7 +15,7 @@ import ( "github.com/sei-protocol/sei-chain/x/nitro/types" ) -func Replay(ctx sdk.Context, txs [][]byte, accounts []types.Account, programs []types.Account) ([]types.Account, error) { +func Replay(ctx sdk.Context, txs [][]byte, accounts []types.Account, sysvarAccounts []types.Account, programs []types.Account) ([]types.Account, error) { // there can be at most one replay per Sei block inputDirectory := fmt.Sprintf("/tmp/replay_input_%d/", ctx.BlockHeight()) if err := os.Mkdir(inputDirectory, os.ModePerm); err != nil { @@ -39,6 +39,14 @@ func Replay(ctx sdk.Context, txs [][]byte, accounts []types.Account, programs [] } accountFilePaths = append(accountFilePaths, path) } + sysvarFilePaths := []string{} + for _, sysvar := range sysvarAccounts { + path, err := writeAccountToFile(inputDirectory, sysvar) + if err != nil { + return nil, err + } + sysvarFilePaths = append(sysvarFilePaths, path) + } programFilePaths := []string{} for _, program := range programs { path, err := writeAccountToFile(inputDirectory, program) @@ -55,7 +63,7 @@ func Replay(ctx sdk.Context, txs [][]byte, accounts []types.Account, programs [] } txFilePaths = append(txFilePaths, path) } - if err := callReplayer(accountFilePaths, programFilePaths, txFilePaths, outputDirectory); err != nil { + if err := callReplayer(accountFilePaths, sysvarFilePaths, programFilePaths, txFilePaths, outputDirectory); err != nil { return nil, err } files, err := ioutil.ReadDir(outputDirectory) diff --git a/x/nitro/replay/replay_test.go b/x/nitro/replay/replay_test.go index c9ee66adab..00947fb11c 100644 --- a/x/nitro/replay/replay_test.go +++ b/x/nitro/replay/replay_test.go @@ -62,7 +62,7 @@ func TestReplay(t *testing.T) { ctx := sdk.Context{}.WithBlockHeight(1) tx := testTransaction() txbz, _ := tx.Marshal() - _, err := Replay(ctx, [][]byte{txbz}, []types.Account{testAccount()}, []types.Account{}) + _, err := Replay(ctx, [][]byte{txbz}, []types.Account{}, []types.Account{testAccount()}, []types.Account{}) require.Nil(t, err) } diff --git a/x/nitro/replay/replayer.h b/x/nitro/replay/replayer.h index e041884f04..598ded0b4d 100644 --- a/x/nitro/replay/replayer.h +++ b/x/nitro/replay/replayer.h @@ -14,6 +14,7 @@ typedef struct FilePaths { } FilePaths; void replay(struct FilePaths account_file_paths, + struct FilePaths sysvar_file_paths, struct FilePaths program_file_paths, struct FilePaths tx_file_paths, struct ByteSliceView output_directory); diff --git a/x/nitro/replay/rustbinding.go b/x/nitro/replay/rustbinding.go index 1421038f34..24d9ef2451 100644 --- a/x/nitro/replay/rustbinding.go +++ b/x/nitro/replay/rustbinding.go @@ -12,7 +12,7 @@ import ( "github.com/sei-protocol/sei-chain/utils" ) -func callReplayer(accountFiles []string, programFiles []string, txFiles []string, outputDir string) (err error) { +func callReplayer(accountFiles []string, sysvarFiles []string, programFiles []string, txFiles []string, outputDir string) (err error) { defer func() { if err := recover(); err != nil { err = fmt.Errorf("received error when calling replayer: %s", err) @@ -24,6 +24,11 @@ func callReplayer(accountFiles []string, programFiles []string, txFiles []string return makeView([]byte(path)) }), ), + makeFilePaths( + utils.Map(sysvarFiles, func(path string) C.ByteSliceView { + return makeView([]byte(path)) + }), + ), makeFilePaths( utils.Map(programFiles, func(path string) C.ByteSliceView { return makeView([]byte(path))