forked from cerc-io/ipld-eth-server
Combine price feed transformers
- fetches logs from all three price feeds in one query - assumes eth/usd price feed will be updated to include LogValue event - updates transformers to run separate from header sync
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds
|
||||
|
||||
var (
|
||||
PepAddress = "0x99041F808D598B782D5a3e498681C2452A31da08"
|
||||
PipAddress = "0x729D19f657BD0614b4985Cf1D82531c67569197B"
|
||||
RepAddress = "0xF5f94b7F9De14D43112e713835BCef2d55b76c1C"
|
||||
)
|
||||
|
||||
type IPriceFeedConfig struct {
|
||||
ContractAddresses []string
|
||||
StartingBlockNumber int64
|
||||
EndingBlockNumber int64
|
||||
}
|
||||
|
||||
var PriceFeedConfig = IPriceFeedConfig{
|
||||
ContractAddresses: []string{
|
||||
PepAddress,
|
||||
PipAddress,
|
||||
RepAddress,
|
||||
},
|
||||
StartingBlockNumber: 0,
|
||||
EndingBlockNumber: 100,
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds
|
||||
|
||||
import (
|
||||
@@ -6,13 +20,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMultipleLogs = errors.New("multiple matching logs found in block")
|
||||
ErrNoMatchingLog = errors.New("no matching log")
|
||||
Ether = big.NewFloat(1e18)
|
||||
PeekMethodName = "peek"
|
||||
PepLogTopic0 = "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527"
|
||||
PipLogTopic0 = "0x1817835800000000000000000000000000000000000000000000000000000000"
|
||||
PipMedianizerABI = `[{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"poke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"poke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"compute","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"unset","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"indexes","outputs":[{"name":"","type":"bytes12"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"next","outputs":[{"name":"","type":"bytes12"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"read","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"peek","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes12"}],"name":"values","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"min_","type":"uint96"}],"name":"setMin","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"void","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"},{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"}],"name":"unset","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"next_","type":"bytes12"}],"name":"setNext","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"min","outputs":[{"name":"","type":"uint96"}],"payable":false,"type":"function"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}]`
|
||||
LogValueTopic0 = "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527"
|
||||
MedianizerABI = `[{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"poke","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"poke","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"compute","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"unset","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"indexes","outputs":[{"name":"","type":"bytes12"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"next","outputs":[{"name":"","type":"bytes12"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"read","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"peek","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes12"}],"name":"values","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"min_","type":"uint96"}],"name":"setMin","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"void","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"},{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"}],"name":"unset","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"next_","type":"bytes12"}],"name":"setNext","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"min","outputs":[{"name":"","type":"uint96"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"val","type":"bytes32"}],"name":"LogValue","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}]]`
|
||||
Ray = big.NewFloat(1e27)
|
||||
RepLogTopic0 = "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
type PriceFeedConverter struct{}
|
||||
|
||||
func (converter PriceFeedConverter) ToModel(log types.Log, headerID int64) PriceFeedModel {
|
||||
return PriceFeedModel{
|
||||
BlockNumber: log.BlockNumber,
|
||||
HeaderID: headerID,
|
||||
MedianizerAddress: log.Address.Bytes(),
|
||||
UsdValue: Convert("wad", hexutil.Encode(log.Data), 15),
|
||||
TransactionIndex: log.TxIndex,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds_test
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
var _ = Describe("Price feed converter", func() {
|
||||
It("converts a log to a price feed model", func() {
|
||||
medianizerAddress := common.HexToAddress("0x99041f808d598b782d5a3e498681c2452a31da08")
|
||||
blockNumber := uint64(6147230)
|
||||
txIndex := uint(119)
|
||||
// https://etherscan.io/tx/0xa51a50a2adbfba4e2ab3d72dfd67a21c769f1bc8d2b180663a15500a56cde58f
|
||||
log := types.Log{
|
||||
Address: medianizerAddress,
|
||||
Topics: []common.Hash{common.HexToHash(price_feeds.LogValueTopic0)},
|
||||
Data: common.FromHex("00000000000000000000000000000000000000000000001486f658319fb0c100"),
|
||||
BlockNumber: blockNumber,
|
||||
TxHash: common.HexToHash("0xa51a50a2adbfba4e2ab3d72dfd67a21c769f1bc8d2b180663a15500a56cde58f"),
|
||||
TxIndex: txIndex,
|
||||
BlockHash: common.HexToHash("0x27ecebbf69eefa3bb3cf65f472322a80ff4946653a50a2171dc605f49829467d"),
|
||||
Index: 0,
|
||||
Removed: false,
|
||||
}
|
||||
converter := price_feeds.PriceFeedConverter{}
|
||||
headerID := int64(123)
|
||||
|
||||
model := converter.ToModel(log, headerID)
|
||||
|
||||
expectedModel := price_feeds.PriceFeedModel{
|
||||
BlockNumber: blockNumber,
|
||||
HeaderID: headerID,
|
||||
MedianizerAddress: medianizerAddress[:],
|
||||
UsdValue: "378.6599388897",
|
||||
TransactionIndex: txIndex,
|
||||
}
|
||||
Expect(model).To(Equal(expectedModel))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type IPriceFeedFetcher interface {
|
||||
FetchLogValues(blockNumber int64) ([]types.Log, error)
|
||||
}
|
||||
|
||||
type PriceFeedFetcher struct {
|
||||
blockChain core.BlockChain
|
||||
contractAddresses []string
|
||||
}
|
||||
|
||||
func NewPriceFeedFetcher(blockChain core.BlockChain, contractAddresses []string) PriceFeedFetcher {
|
||||
return PriceFeedFetcher{
|
||||
blockChain: blockChain,
|
||||
contractAddresses: contractAddresses,
|
||||
}
|
||||
}
|
||||
|
||||
func (fetcher PriceFeedFetcher) FetchLogValues(blockNumber int64) ([]types.Log, error) {
|
||||
var addresses []common.Address
|
||||
for _, addr := range fetcher.contractAddresses {
|
||||
addresses = append(addresses, common.HexToAddress(addr))
|
||||
}
|
||||
n := big.NewInt(blockNumber)
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: n,
|
||||
ToBlock: n,
|
||||
Addresses: addresses,
|
||||
Topics: [][]common.Hash{{common.HexToHash(LogValueTopic0)}},
|
||||
}
|
||||
return fetcher.blockChain.GetEthLogsWithCustomQuery(query)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
var _ = Describe("Price fetcher", func() {
|
||||
It("gets log value events from price feed medianizers", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{}})
|
||||
contractAddresses := []string{"pep-contract-address", "pip-contract-address", "rep-contract-address"}
|
||||
fetcher := price_feeds.NewPriceFeedFetcher(mockBlockChain, contractAddresses)
|
||||
blockNumber := int64(100)
|
||||
|
||||
_, err := fetcher.FetchLogValues(blockNumber)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var expectedAddresses []common.Address
|
||||
for _, address := range contractAddresses {
|
||||
expectedAddresses = append(expectedAddresses, common.HexToAddress(address))
|
||||
}
|
||||
expectedQuery := ethereum.FilterQuery{
|
||||
FromBlock: big.NewInt(blockNumber),
|
||||
ToBlock: big.NewInt(blockNumber),
|
||||
Addresses: expectedAddresses,
|
||||
Topics: [][]common.Hash{{common.HexToHash(price_feeds.LogValueTopic0)}},
|
||||
}
|
||||
mockBlockChain.AssertGetEthLogsWithCustomQueryCalledWith(expectedQuery)
|
||||
})
|
||||
|
||||
It("returns error if getting logs fails", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryErr(fakes.FakeError)
|
||||
fetcher := price_feeds.NewPriceFeedFetcher(mockBlockChain, []string{"contract-address"})
|
||||
|
||||
_, err := fetcher.FetchLogValues(100)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
})
|
||||
@@ -1,53 +0,0 @@
|
||||
package pep
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
type IPepFetcher interface {
|
||||
FetchPepValue(header core.Header) (string, error)
|
||||
}
|
||||
|
||||
type PepFetcher struct {
|
||||
blockChain core.BlockChain
|
||||
contractAddress string
|
||||
}
|
||||
|
||||
func NewPepFetcher(chain core.BlockChain, contractAddress string) PepFetcher {
|
||||
return PepFetcher{
|
||||
blockChain: chain,
|
||||
contractAddress: contractAddress,
|
||||
}
|
||||
}
|
||||
|
||||
func (fetcher PepFetcher) FetchPepValue(header core.Header) (string, error) {
|
||||
blockNumber := big.NewInt(header.BlockNumber)
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: blockNumber,
|
||||
ToBlock: blockNumber,
|
||||
Addresses: []common.Address{common.HexToAddress(fetcher.contractAddress)},
|
||||
Topics: [][]common.Hash{{common.HexToHash(price_feeds.PepLogTopic0)}},
|
||||
}
|
||||
logs, err := fetcher.blockChain.GetEthLogsWithCustomQuery(query)
|
||||
return fetcher.getLogValue(logs, err)
|
||||
}
|
||||
|
||||
func (fetcher PepFetcher) getLogValue(logs []types.Log, err error) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(logs) < 1 {
|
||||
return "", price_feeds.ErrNoMatchingLog
|
||||
}
|
||||
if len(logs) > 1 {
|
||||
return "", price_feeds.ErrMultipleLogs
|
||||
}
|
||||
return string(logs[0].Data), nil
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package pep_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pep"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var _ = Describe("Pep fetcher", func() {
|
||||
It("gets logs describing updated mkr/usd value", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{}})
|
||||
contractAddress := "pep-contract-address"
|
||||
fetcher := pep.NewPepFetcher(mockBlockChain, contractAddress)
|
||||
blockNumber := int64(12345)
|
||||
header := core.Header{
|
||||
BlockNumber: blockNumber,
|
||||
Hash: "",
|
||||
Raw: nil,
|
||||
}
|
||||
|
||||
_, err := fetcher.FetchPepValue(header)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedQuery := ethereum.FilterQuery{
|
||||
FromBlock: big.NewInt(blockNumber),
|
||||
ToBlock: big.NewInt(blockNumber),
|
||||
Addresses: []common.Address{common.HexToAddress(contractAddress)},
|
||||
Topics: [][]common.Hash{{common.HexToHash(price_feeds.PepLogTopic0)}},
|
||||
}
|
||||
mockBlockChain.AssertGetEthLogsWithCustomQueryCalledWith(expectedQuery)
|
||||
})
|
||||
|
||||
It("returns error if getting logs fails", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{}})
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryErr(fakes.FakeError)
|
||||
fetcher := pep.NewPepFetcher(mockBlockChain, "pep-contract-address")
|
||||
|
||||
_, err := fetcher.FetchPepValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("returns no matching logs error if no logs returned", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
fetcher := pep.NewPepFetcher(mockBlockChain, "pep-contract-address")
|
||||
|
||||
_, err := fetcher.FetchPepValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(price_feeds.ErrNoMatchingLog))
|
||||
})
|
||||
|
||||
It("returns error if more than one matching logs returned", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{}, {}})
|
||||
fetcher := pep.NewPepFetcher(mockBlockChain, "pep-contract-address")
|
||||
|
||||
_, err := fetcher.FetchPepValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(price_feeds.ErrMultipleLogs))
|
||||
})
|
||||
})
|
||||
@@ -1,13 +0,0 @@
|
||||
package pep
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestPep(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Pep Suite")
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package pep
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
type IPepRepository interface {
|
||||
CreatePep(pep price_feeds.PriceUpdate) error
|
||||
}
|
||||
|
||||
type PepRepository struct {
|
||||
db *postgres.DB
|
||||
}
|
||||
|
||||
func NewPepRepository(db *postgres.DB) PepRepository {
|
||||
return PepRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (repository PepRepository) CreatePep(pep price_feeds.PriceUpdate) error {
|
||||
_, err := repository.db.Exec(`INSERT INTO maker.peps (block_number, header_id, usd_value) VALUES ($1, $2, $3::NUMERIC)`, pep.BlockNumber, pep.HeaderID, pep.UsdValue)
|
||||
return err
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package pep_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pep"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("Pep repository", func() {
|
||||
It("returns header if matching header does not exist", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
repository := pep.NewPepRepository(db)
|
||||
pepToAdd := price_feeds.PriceUpdate{
|
||||
BlockNumber: 0,
|
||||
HeaderID: 0,
|
||||
UsdValue: "123.456",
|
||||
}
|
||||
|
||||
err := repository.CreatePep(pepToAdd)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("creates a pep when matching header exists", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
repository := pep.NewPepRepository(db)
|
||||
header := core.Header{BlockNumber: 12345}
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
pepToAdd := price_feeds.PriceUpdate{
|
||||
BlockNumber: header.BlockNumber,
|
||||
HeaderID: headerID,
|
||||
UsdValue: "123.456",
|
||||
}
|
||||
|
||||
err = repository.CreatePep(pepToAdd)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var dbPep price_feeds.PriceUpdate
|
||||
err = db.Get(&dbPep, `SELECT block_number, header_id, usd_value FROM maker.peps WHERE header_id = $1`, pepToAdd.HeaderID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbPep).To(Equal(pepToAdd))
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
package pep
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
type PepTransformer struct {
|
||||
fetcher IPepFetcher
|
||||
repository IPepRepository
|
||||
}
|
||||
|
||||
func NewPepTransformer(chain core.BlockChain, db *postgres.DB, contractAddress string) PepTransformer {
|
||||
fetcher := NewPepFetcher(chain, contractAddress)
|
||||
repository := NewPepRepository(db)
|
||||
return PepTransformer{
|
||||
fetcher: fetcher,
|
||||
repository: repository,
|
||||
}
|
||||
}
|
||||
|
||||
func (transformer PepTransformer) Execute(header core.Header, headerID int64) error {
|
||||
logValue, err := transformer.fetcher.FetchPepValue(header)
|
||||
if err != nil {
|
||||
if err == price_feeds.ErrNoMatchingLog {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
pep := getPep(logValue, header, headerID)
|
||||
return transformer.repository.CreatePep(pep)
|
||||
}
|
||||
|
||||
func getPep(logValue string, header core.Header, headerID int64) price_feeds.PriceUpdate {
|
||||
valueInUSD := price_feeds.Convert("wad", logValue, 15)
|
||||
pep := price_feeds.PriceUpdate{
|
||||
BlockNumber: header.BlockNumber,
|
||||
HeaderID: headerID,
|
||||
UsdValue: valueInUSD,
|
||||
}
|
||||
return pep
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package pep_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pep"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("Pep transformer", func() {
|
||||
It("returns nil if no logs found", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
transformer := pep.NewPepTransformer(chain, db, "pep-contract-address")
|
||||
|
||||
err := transformer.Execute(core.Header{}, 123)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("creates pep row for found log", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
chain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{Data: []byte{1, 2, 3, 4, 5}}})
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
header := core.Header{BlockNumber: 12345}
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
transformer := pep.NewPepTransformer(chain, db, "pep-contract-address")
|
||||
|
||||
err = transformer.Execute(header, headerID)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var dbPep price_feeds.PriceUpdate
|
||||
err = db.Get(&dbPep, `SELECT block_number, header_id, usd_value FROM maker.peps WHERE header_id = $1`, headerID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbPep.BlockNumber).To(Equal(header.BlockNumber))
|
||||
})
|
||||
})
|
||||
@@ -1,77 +0,0 @@
|
||||
package pip
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type IPipFetcher interface {
|
||||
FetchPipValue(header core.Header) (string, error)
|
||||
}
|
||||
|
||||
type PipFetcher struct {
|
||||
blockChain core.BlockChain
|
||||
contractAddress string
|
||||
}
|
||||
|
||||
func NewPipFetcher(chain core.BlockChain, contractAddress string) PipFetcher {
|
||||
return PipFetcher{
|
||||
blockChain: chain,
|
||||
contractAddress: contractAddress,
|
||||
}
|
||||
}
|
||||
|
||||
func (fetcher PipFetcher) FetchPipValue(header core.Header) (string, error) {
|
||||
blockNumber := big.NewInt(header.BlockNumber)
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: blockNumber,
|
||||
ToBlock: blockNumber,
|
||||
Addresses: []common.Address{common.HexToAddress(fetcher.contractAddress)},
|
||||
Topics: [][]common.Hash{{common.HexToHash(price_feeds.PipLogTopic0)}},
|
||||
}
|
||||
logs, err := fetcher.blockChain.GetEthLogsWithCustomQuery(query)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(logs) > 0 {
|
||||
return fetcher.getLogValue(logs, err)
|
||||
}
|
||||
return "", price_feeds.ErrNoMatchingLog
|
||||
}
|
||||
|
||||
func (fetcher PipFetcher) getLogValue(logs []types.Log, err error) (string, error) {
|
||||
var (
|
||||
ret0 = new([32]byte)
|
||||
ret1 = new(bool)
|
||||
)
|
||||
var r = &[]interface{}{
|
||||
ret0,
|
||||
ret1,
|
||||
}
|
||||
err = fetcher.blockChain.FetchContractData(price_feeds.PipMedianizerABI, fetcher.contractAddress, price_feeds.PeekMethodName, nil, r, int64(logs[0].BlockNumber))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := newResult(*ret0, *ret1)
|
||||
return result.Value.String(), nil
|
||||
}
|
||||
|
||||
type Value [32]byte
|
||||
|
||||
type Peek struct {
|
||||
Value
|
||||
OK bool
|
||||
}
|
||||
|
||||
func (value Value) String() string {
|
||||
bi := big.NewInt(0).SetBytes(value[:])
|
||||
return bi.String()
|
||||
}
|
||||
|
||||
func newResult(value [32]byte, ok bool) *Peek {
|
||||
return &Peek{Value: value, OK: ok}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package pip_test
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pip"
|
||||
)
|
||||
|
||||
var _ = Describe("Pip fetcher", func() {
|
||||
It("returns error if fetching logs fails", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
chain.SetGetEthLogsWithCustomQueryErr(fakes.FakeError)
|
||||
fetcher := pip.NewPipFetcher(chain, "pip-contract-address")
|
||||
|
||||
_, err := fetcher.FetchPipValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("returns no matching logs error if no logs returned", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
fetcher := pip.NewPipFetcher(chain, "pip-contract-address")
|
||||
|
||||
_, err := fetcher.FetchPipValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(price_feeds.ErrNoMatchingLog))
|
||||
})
|
||||
|
||||
Describe("when matching log found", func() {
|
||||
It("calls contract to peek current eth/usd value", func() {
|
||||
blockNumber := uint64(12345)
|
||||
chain := fakes.NewMockBlockChain()
|
||||
chain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{BlockNumber: blockNumber}})
|
||||
contractAddress := "pip-contract-address"
|
||||
fetcher := pip.NewPipFetcher(chain, contractAddress)
|
||||
|
||||
_, err := fetcher.FetchPipValue(core.Header{})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
chain.AssertFetchContractDataCalledWith(price_feeds.PipMedianizerABI, contractAddress, price_feeds.PeekMethodName, nil, &[]interface{}{[32]byte{}, false}, int64(blockNumber))
|
||||
})
|
||||
|
||||
It("returns error if contract call fails", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
chain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{BlockNumber: uint64(12345)}})
|
||||
chain.SetFetchContractDataErr(fakes.FakeError)
|
||||
fetcher := pip.NewPipFetcher(chain, "pip-contract-address")
|
||||
|
||||
_, err := fetcher.FetchPipValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,13 +0,0 @@
|
||||
package pip
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestPip(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Pip Suite")
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package pip
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
type IPipRepository interface {
|
||||
CreatePip(pip price_feeds.PriceUpdate) error
|
||||
}
|
||||
|
||||
type PipRepository struct {
|
||||
db *postgres.DB
|
||||
}
|
||||
|
||||
func NewPipRepository(db *postgres.DB) PipRepository {
|
||||
return PipRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (repository PipRepository) CreatePip(pip price_feeds.PriceUpdate) error {
|
||||
_, err := repository.db.Exec(`INSERT INTO maker.pips (block_number, header_id, usd_value) VALUES ($1, $2, $3::NUMERIC)`, pip.BlockNumber, pip.HeaderID, pip.UsdValue)
|
||||
return err
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package pip_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pip"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("Pip repository", func() {
|
||||
It("does not create a pip if no matching header", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
repository := pip.NewPipRepository(db)
|
||||
priceUpdate := price_feeds.PriceUpdate{
|
||||
BlockNumber: 0,
|
||||
HeaderID: 0,
|
||||
UsdValue: "123",
|
||||
}
|
||||
|
||||
err := repository.CreatePip(priceUpdate)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("creates a pip when header exists", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
repository := pip.NewPipRepository(db)
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
header := core.Header{BlockNumber: 12345}
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
priceUpdate := price_feeds.PriceUpdate{
|
||||
BlockNumber: header.BlockNumber,
|
||||
HeaderID: headerID,
|
||||
UsdValue: "777.777",
|
||||
}
|
||||
|
||||
err = repository.CreatePip(priceUpdate)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var dbPip price_feeds.PriceUpdate
|
||||
err = db.Get(&dbPip, `SELECT block_number, header_id, usd_value FROM maker.pips WHERE block_number = $1`, header.BlockNumber)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbPip).To(Equal(priceUpdate))
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
package pip
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
type PipTransformer struct {
|
||||
fetcher IPipFetcher
|
||||
repository IPipRepository
|
||||
}
|
||||
|
||||
func NewPipTransformer(chain core.BlockChain, db *postgres.DB, contractAddress string) PipTransformer {
|
||||
fetcher := NewPipFetcher(chain, contractAddress)
|
||||
repository := NewPipRepository(db)
|
||||
return PipTransformer{
|
||||
fetcher: fetcher,
|
||||
repository: repository,
|
||||
}
|
||||
}
|
||||
|
||||
func (transformer PipTransformer) Execute(header core.Header, headerID int64) error {
|
||||
value, err := transformer.fetcher.FetchPipValue(header)
|
||||
if err != nil {
|
||||
if err == price_feeds.ErrNoMatchingLog {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
pip := getPip(value, header, headerID)
|
||||
return transformer.repository.CreatePip(pip)
|
||||
}
|
||||
|
||||
func getPip(logValue string, header core.Header, headerID int64) price_feeds.PriceUpdate {
|
||||
valueInUSD := price_feeds.Convert("wad", logValue, 15)
|
||||
pep := price_feeds.PriceUpdate{
|
||||
BlockNumber: header.BlockNumber,
|
||||
HeaderID: headerID,
|
||||
UsdValue: valueInUSD,
|
||||
}
|
||||
return pep
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package pip_test
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pip"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("Pip transformer", func() {
|
||||
It("returns nil if no logs found", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
transformer := pip.NewPipTransformer(chain, db, "pip-contract-address")
|
||||
|
||||
err := transformer.Execute(core.Header{}, 123)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("creates pip row for found log", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
chain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{Data: []byte{1, 2, 3, 4, 5}}})
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
header := core.Header{BlockNumber: 12345}
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
transformer := pip.NewPipTransformer(chain, db, "pip-contract-address")
|
||||
|
||||
err = transformer.Execute(header, headerID)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var dbPip price_feeds.PriceUpdate
|
||||
err = db.Get(&dbPip, `SELECT block_number, header_id, usd_value FROM maker.pips WHERE header_id = $1`, headerID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbPip.BlockNumber).To(Equal(header.BlockNumber))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestPriceFeeds(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "PriceFeeds Suite")
|
||||
}
|
||||
@@ -1,11 +1,34 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds
|
||||
|
||||
import "math/big"
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type PriceUpdate struct {
|
||||
BlockNumber int64 `db:"block_number"`
|
||||
HeaderID int64 `db:"header_id"`
|
||||
UsdValue string `db:"usd_value"`
|
||||
type LogValueEntity struct {
|
||||
Val common.Address
|
||||
}
|
||||
|
||||
type PriceFeedModel struct {
|
||||
BlockNumber uint64 `db:"block_number"`
|
||||
HeaderID int64 `db:"header_id"`
|
||||
MedianizerAddress []byte `db:"medianizer_address"`
|
||||
UsdValue string `db:"usd_value"`
|
||||
TransactionIndex uint `db:"tx_idx"`
|
||||
}
|
||||
|
||||
func Convert(conversion string, value string, prec int) string {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package rep
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type IRepFetcher interface {
|
||||
FetchRepValue(header core.Header) (string, error)
|
||||
}
|
||||
|
||||
type RepFetcher struct {
|
||||
chain core.BlockChain
|
||||
contractAddress string
|
||||
}
|
||||
|
||||
func NewRepFetcher(chain core.BlockChain, contractAddress string) RepFetcher {
|
||||
return RepFetcher{
|
||||
chain: chain,
|
||||
contractAddress: contractAddress,
|
||||
}
|
||||
}
|
||||
|
||||
func (fetcher RepFetcher) FetchRepValue(header core.Header) (string, error) {
|
||||
blockNumber := big.NewInt(header.BlockNumber)
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: blockNumber,
|
||||
ToBlock: blockNumber,
|
||||
Addresses: []common.Address{common.HexToAddress(fetcher.contractAddress)},
|
||||
Topics: [][]common.Hash{{common.HexToHash(price_feeds.RepLogTopic0)}},
|
||||
}
|
||||
logs, err := fetcher.chain.GetEthLogsWithCustomQuery(query)
|
||||
return fetcher.getLogValue(logs, err)
|
||||
}
|
||||
|
||||
func (fetcher RepFetcher) getLogValue(logs []types.Log, err error) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(logs) < 1 {
|
||||
return "", price_feeds.ErrNoMatchingLog
|
||||
}
|
||||
if len(logs) > 1 {
|
||||
return "", price_feeds.ErrMultipleLogs
|
||||
}
|
||||
return string(logs[0].Data), nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package rep_test
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var _ = Describe("Rep fetcher", func() {
|
||||
It("gets logs describing updated rep/usd value", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{}})
|
||||
contractAddress := "rep-contract-address"
|
||||
fetcher := rep.NewRepFetcher(mockBlockChain, contractAddress)
|
||||
blockNumber := int64(100)
|
||||
header := core.Header{
|
||||
BlockNumber: blockNumber,
|
||||
Hash: "",
|
||||
Raw: nil,
|
||||
}
|
||||
|
||||
_, err := fetcher.FetchRepValue(header)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedQuery := ethereum.FilterQuery{
|
||||
FromBlock: big.NewInt(blockNumber),
|
||||
ToBlock: big.NewInt(blockNumber),
|
||||
Addresses: []common.Address{common.HexToAddress(contractAddress)},
|
||||
Topics: [][]common.Hash{{common.HexToHash(price_feeds.RepLogTopic0)}},
|
||||
}
|
||||
mockBlockChain.AssertGetEthLogsWithCustomQueryCalledWith(expectedQuery)
|
||||
})
|
||||
|
||||
It("returns error if getting logs fails", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryErr(fakes.FakeError)
|
||||
fetcher := rep.NewRepFetcher(mockBlockChain, "rep-contract-address")
|
||||
|
||||
_, err := fetcher.FetchRepValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("returns no matching logs error if no logs returned", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
fetcher := rep.NewRepFetcher(mockBlockChain, "rep-contract-address")
|
||||
|
||||
_, err := fetcher.FetchRepValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(price_feeds.ErrNoMatchingLog))
|
||||
})
|
||||
|
||||
It("returns error if more than one matching logs returned", func() {
|
||||
mockBlockChain := fakes.NewMockBlockChain()
|
||||
mockBlockChain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{}, {}})
|
||||
fetcher := rep.NewRepFetcher(mockBlockChain, "rep-contract-address")
|
||||
|
||||
_, err := fetcher.FetchRepValue(core.Header{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(price_feeds.ErrMultipleLogs))
|
||||
})
|
||||
})
|
||||
@@ -1,13 +0,0 @@
|
||||
package rep_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestRep(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Rep Suite")
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package rep
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
type IRepRepository interface {
|
||||
CreateRep(rep price_feeds.PriceUpdate) error
|
||||
}
|
||||
|
||||
type RepRepository struct {
|
||||
db *postgres.DB
|
||||
}
|
||||
|
||||
func NewRepRepository(db *postgres.DB) RepRepository {
|
||||
return RepRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (repository RepRepository) CreateRep(rep price_feeds.PriceUpdate) error {
|
||||
_, err := repository.db.Exec(`INSERT INTO maker.reps (block_number, header_id, usd_value) VALUES ($1, $2, $3::NUMERIC)`, rep.BlockNumber, rep.HeaderID, rep.UsdValue)
|
||||
return err
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package rep_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("Rep repository", func() {
|
||||
It("returns header if matching header does not exist", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
repository := rep.NewRepRepository(db)
|
||||
pepToAdd := price_feeds.PriceUpdate{
|
||||
BlockNumber: 0,
|
||||
HeaderID: 0,
|
||||
UsdValue: "123.456",
|
||||
}
|
||||
|
||||
err := repository.CreateRep(pepToAdd)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("creates a rep when matching header exists", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
repository := rep.NewRepRepository(db)
|
||||
header := core.Header{BlockNumber: 12345}
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
pepToAdd := price_feeds.PriceUpdate{
|
||||
BlockNumber: header.BlockNumber,
|
||||
HeaderID: headerID,
|
||||
UsdValue: "123.456",
|
||||
}
|
||||
|
||||
err = repository.CreateRep(pepToAdd)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var dbRep price_feeds.PriceUpdate
|
||||
err = db.Get(&dbRep, `SELECT block_number, header_id, usd_value FROM maker.reps WHERE header_id = $1`, pepToAdd.HeaderID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbRep).To(Equal(pepToAdd))
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
package rep
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
)
|
||||
|
||||
type RepTransformer struct {
|
||||
fetcher IRepFetcher
|
||||
repository IRepRepository
|
||||
}
|
||||
|
||||
func NewRepTransformer(chain core.BlockChain, db *postgres.DB, contractAddress string) RepTransformer {
|
||||
fetcher := NewRepFetcher(chain, contractAddress)
|
||||
repository := NewRepRepository(db)
|
||||
return RepTransformer{
|
||||
fetcher: fetcher,
|
||||
repository: repository,
|
||||
}
|
||||
}
|
||||
|
||||
func (transformer RepTransformer) Execute(header core.Header, headerID int64) error {
|
||||
logValue, err := transformer.fetcher.FetchRepValue(header)
|
||||
if err != nil {
|
||||
if err == price_feeds.ErrNoMatchingLog {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
rep := getRep(logValue, header, headerID)
|
||||
return transformer.repository.CreateRep(rep)
|
||||
}
|
||||
|
||||
func getRep(logValue string, header core.Header, headerID int64) price_feeds.PriceUpdate {
|
||||
valueInUSD := price_feeds.Convert("wad", logValue, 15)
|
||||
rep := price_feeds.PriceUpdate{
|
||||
BlockNumber: header.BlockNumber,
|
||||
HeaderID: headerID,
|
||||
UsdValue: valueInUSD,
|
||||
}
|
||||
return rep
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package rep_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("Rep transformer", func() {
|
||||
It("returns nil if no logs found", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
transformer := rep.NewRepTransformer(chain, db, "rep-contract-address")
|
||||
|
||||
err := transformer.Execute(core.Header{}, 123)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("creates rep row for found log", func() {
|
||||
chain := fakes.NewMockBlockChain()
|
||||
chain.SetGetEthLogsWithCustomQueryReturnLogs([]types.Log{{Data: []byte{1, 2, 3, 4, 5}}})
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
header := core.Header{BlockNumber: 12345}
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
transformer := rep.NewRepTransformer(chain, db, "rep-contract-address")
|
||||
|
||||
err = transformer.Execute(header, headerID)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var dbRep price_feeds.PriceUpdate
|
||||
err = db.Get(&dbRep, `SELECT block_number, header_id, usd_value FROM maker.reps WHERE header_id = $1`, headerID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbRep.BlockNumber).To(Equal(header.BlockNumber))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
)
|
||||
|
||||
type IPriceFeedRepository interface {
|
||||
Create(model PriceFeedModel) error
|
||||
MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error)
|
||||
}
|
||||
|
||||
type PriceFeedRepository struct {
|
||||
db *postgres.DB
|
||||
}
|
||||
|
||||
func NewPriceFeedRepository(db *postgres.DB) PriceFeedRepository {
|
||||
return PriceFeedRepository{db: db}
|
||||
}
|
||||
|
||||
func (repository PriceFeedRepository) Create(model PriceFeedModel) error {
|
||||
_, err := repository.db.Exec(`INSERT INTO maker.price_feeds (block_number, header_id, medianizer_address, usd_value, tx_idx)
|
||||
VALUES ($1, $2, $3, $4::NUMERIC, $5)`, model.BlockNumber, model.HeaderID, model.MedianizerAddress, model.UsdValue, model.TransactionIndex)
|
||||
return err
|
||||
}
|
||||
|
||||
func (repository PriceFeedRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) {
|
||||
var result []core.Header
|
||||
err := repository.db.Select(
|
||||
&result,
|
||||
`SELECT headers.id, headers.block_number FROM headers
|
||||
LEFT JOIN maker.price_feeds on headers.id = header_id
|
||||
WHERE header_id ISNULL
|
||||
AND headers.block_number >= $1
|
||||
AND headers.block_number <= $2
|
||||
AND headers.eth_node_fingerprint = $3`,
|
||||
startingBlockNumber,
|
||||
endingBlockNumber,
|
||||
repository.db.Node.ID,
|
||||
)
|
||||
return result, err
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("Price feeds repository", func() {
|
||||
Describe("Create", func() {
|
||||
It("persists a price feed update", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
blockNumber := uint64(12345)
|
||||
header := core.Header{BlockNumber: int64(blockNumber)}
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
priceFeedUpdate := price_feeds.PriceFeedModel{
|
||||
BlockNumber: blockNumber,
|
||||
HeaderID: headerID,
|
||||
MedianizerAddress: []byte{1, 2, 3, 4, 5},
|
||||
UsdValue: "123.45",
|
||||
TransactionIndex: 1,
|
||||
}
|
||||
priceFeedRepository := price_feeds.NewPriceFeedRepository(db)
|
||||
|
||||
err = priceFeedRepository.Create(priceFeedUpdate)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var dbPriceFeedUpdate price_feeds.PriceFeedModel
|
||||
err = db.Get(&dbPriceFeedUpdate, `SELECT block_number, header_id, medianizer_address, usd_value, tx_idx FROM maker.price_feeds WHERE header_id = $1`, headerID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbPriceFeedUpdate).To(Equal(priceFeedUpdate))
|
||||
})
|
||||
|
||||
It("does not duplicate price feed updates", func() {
|
||||
db := test_config.NewTestDB(core.Node{})
|
||||
test_config.CleanTestDB(db)
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
blockNumber := uint64(12345)
|
||||
header := core.Header{BlockNumber: int64(blockNumber)}
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(header)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
priceFeedUpdate := price_feeds.PriceFeedModel{
|
||||
BlockNumber: blockNumber,
|
||||
HeaderID: headerID,
|
||||
MedianizerAddress: []byte{1, 2, 3, 4, 5},
|
||||
UsdValue: "123.45",
|
||||
TransactionIndex: 1,
|
||||
}
|
||||
priceFeedRepository := price_feeds.NewPriceFeedRepository(db)
|
||||
err = priceFeedRepository.Create(priceFeedUpdate)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = priceFeedRepository.Create(priceFeedUpdate)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("MissingHeaders", func() {
|
||||
It("returns headers with no associated price feed event", func() {
|
||||
node := core.Node{}
|
||||
db := test_config.NewTestDB(node)
|
||||
test_config.CleanTestDB(db)
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
startingBlockNumber := int64(1)
|
||||
priceFeedBlockNumber := int64(2)
|
||||
endingBlockNumber := int64(3)
|
||||
blockNumbers := []int64{startingBlockNumber, priceFeedBlockNumber, endingBlockNumber, endingBlockNumber + 1}
|
||||
var headerIDs []int64
|
||||
for _, n := range blockNumbers {
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: n})
|
||||
headerIDs = append(headerIDs, headerID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
priceFeedRepository := price_feeds.NewPriceFeedRepository(db)
|
||||
priceFeedUpdate := price_feeds.PriceFeedModel{
|
||||
BlockNumber: uint64(blockNumbers[1]),
|
||||
HeaderID: headerIDs[1],
|
||||
UsdValue: "123.45",
|
||||
}
|
||||
err := priceFeedRepository.Create(priceFeedUpdate)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
headers, err := priceFeedRepository.MissingHeaders(startingBlockNumber, endingBlockNumber)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(headers)).To(Equal(2))
|
||||
Expect(headers[0].BlockNumber).To(Or(Equal(startingBlockNumber), Equal(endingBlockNumber)))
|
||||
Expect(headers[1].BlockNumber).To(Or(Equal(startingBlockNumber), Equal(endingBlockNumber)))
|
||||
})
|
||||
|
||||
It("only returns headers associated with the current node", func() {
|
||||
nodeOne := core.Node{}
|
||||
db := test_config.NewTestDB(nodeOne)
|
||||
test_config.CleanTestDB(db)
|
||||
blockNumbers := []int64{1, 2, 3}
|
||||
headerRepository := repositories.NewHeaderRepository(db)
|
||||
nodeTwo := core.Node{ID: "second"}
|
||||
dbTwo := test_config.NewTestDB(nodeTwo)
|
||||
headerRepositoryTwo := repositories.NewHeaderRepository(dbTwo)
|
||||
var headerIDs []int64
|
||||
for _, n := range blockNumbers {
|
||||
headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: n})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
headerIDs = append(headerIDs, headerID)
|
||||
_, err = headerRepositoryTwo.CreateOrUpdateHeader(core.Header{BlockNumber: n})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
priceFeedRepository := price_feeds.NewPriceFeedRepository(db)
|
||||
priceFeedRepositoryTwo := price_feeds.NewPriceFeedRepository(dbTwo)
|
||||
err := priceFeedRepository.Create(price_feeds.PriceFeedModel{
|
||||
HeaderID: headerIDs[0],
|
||||
UsdValue: "123.45",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
nodeOneMissingHeaders, err := priceFeedRepository.MissingHeaders(blockNumbers[0], blockNumbers[len(blockNumbers)-1])
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(nodeOneMissingHeaders)).To(Equal(len(blockNumbers) - 1))
|
||||
|
||||
nodeTwoMissingHeaders, err := priceFeedRepositoryTwo.MissingHeaders(blockNumbers[0], blockNumbers[len(blockNumbers)-1])
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(nodeTwoMissingHeaders)).To(Equal(len(blockNumbers)))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/shared"
|
||||
)
|
||||
|
||||
type PriceFeedTransformerInitializer struct {
|
||||
Config IPriceFeedConfig
|
||||
}
|
||||
|
||||
func (initializer PriceFeedTransformerInitializer) NewPriceFeedTransformer(db *postgres.DB, blockChain core.BlockChain) shared.Transformer {
|
||||
converter := PriceFeedConverter{}
|
||||
fetcher := NewPriceFeedFetcher(blockChain, initializer.Config.ContractAddresses)
|
||||
repository := NewPriceFeedRepository(db)
|
||||
return PriceFeedTransformer{
|
||||
Config: initializer.Config,
|
||||
converter: converter,
|
||||
Fetcher: fetcher,
|
||||
Repository: repository,
|
||||
}
|
||||
}
|
||||
|
||||
type PriceFeedTransformer struct {
|
||||
Config IPriceFeedConfig
|
||||
converter PriceFeedConverter
|
||||
Fetcher IPriceFeedFetcher
|
||||
Repository IPriceFeedRepository
|
||||
}
|
||||
|
||||
func (transformer PriceFeedTransformer) Execute() error {
|
||||
headers, err := transformer.Repository.MissingHeaders(transformer.Config.StartingBlockNumber, transformer.Config.EndingBlockNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, header := range headers {
|
||||
logs, err := transformer.Fetcher.FetchLogValues(header.BlockNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, log := range logs {
|
||||
model := transformer.converter.ToModel(log, header.Id)
|
||||
err = transformer.Repository.Create(model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright © 2018 Vulcanize
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package price_feeds_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
|
||||
price_feeds2 "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks/price_feeds"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var _ = Describe("Price feed transformer", func() {
|
||||
It("gets missing headers for price feeds", func() {
|
||||
mockRepository := &price_feeds2.MockPriceFeedRepository{}
|
||||
transformer := price_feeds.PriceFeedTransformer{
|
||||
Config: price_feeds.PriceFeedConfig,
|
||||
Fetcher: &price_feeds2.MockPriceFeedFetcher{},
|
||||
Repository: mockRepository,
|
||||
}
|
||||
|
||||
err := transformer.Execute()
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockRepository.AssertMissingHeadersCalledwith(price_feeds.PriceFeedConfig.StartingBlockNumber, price_feeds.PriceFeedConfig.EndingBlockNumber)
|
||||
})
|
||||
|
||||
It("returns error is missing headers call returns err", func() {
|
||||
mockRepository := &price_feeds2.MockPriceFeedRepository{}
|
||||
mockRepository.SetMissingHeadersErr(fakes.FakeError)
|
||||
transformer := price_feeds.PriceFeedTransformer{
|
||||
Fetcher: &price_feeds2.MockPriceFeedFetcher{},
|
||||
Repository: mockRepository,
|
||||
}
|
||||
|
||||
err := transformer.Execute()
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("fetches logs for missing headers", func() {
|
||||
mockRepository := &price_feeds2.MockPriceFeedRepository{}
|
||||
blockNumberOne := int64(1)
|
||||
blockNumberTwo := int64(2)
|
||||
mockRepository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumberOne}, {BlockNumber: blockNumberTwo}})
|
||||
mockFetcher := &price_feeds2.MockPriceFeedFetcher{}
|
||||
transformer := price_feeds.PriceFeedTransformer{
|
||||
Fetcher: mockFetcher,
|
||||
Repository: mockRepository,
|
||||
}
|
||||
|
||||
err := transformer.Execute()
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockFetcher.AssertFetchLogValuesCalledWith([]int64{blockNumberOne, blockNumberTwo})
|
||||
})
|
||||
|
||||
It("returns err if fetcher returns err", func() {
|
||||
mockRepository := &price_feeds2.MockPriceFeedRepository{}
|
||||
mockRepository.SetMissingHeaders([]core.Header{{BlockNumber: 1}})
|
||||
mockFetcher := &price_feeds2.MockPriceFeedFetcher{}
|
||||
mockFetcher.SetReturnErr(fakes.FakeError)
|
||||
transformer := price_feeds.PriceFeedTransformer{
|
||||
Fetcher: mockFetcher,
|
||||
Repository: mockRepository,
|
||||
}
|
||||
|
||||
err := transformer.Execute()
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("persists model converted from log", func() {
|
||||
mockRepository := &price_feeds2.MockPriceFeedRepository{}
|
||||
headerID := int64(11111)
|
||||
mockRepository.SetMissingHeaders([]core.Header{{BlockNumber: 1, Id: headerID}})
|
||||
mockFetcher := &price_feeds2.MockPriceFeedFetcher{}
|
||||
blockNumber := uint64(22222)
|
||||
txIndex := uint(33333)
|
||||
usdValue := int64(44444)
|
||||
etherMultiplier, _ := price_feeds.Ether.Int64()
|
||||
rawUsdValue := big.NewInt(0)
|
||||
rawUsdValue = rawUsdValue.Mul(big.NewInt(usdValue), big.NewInt(etherMultiplier))
|
||||
address := common.BytesToAddress([]byte{1, 2, 3, 4, 5})
|
||||
fakeLog := types.Log{
|
||||
Address: address,
|
||||
Topics: nil,
|
||||
Data: rawUsdValue.Bytes(),
|
||||
BlockNumber: blockNumber,
|
||||
TxHash: common.Hash{},
|
||||
TxIndex: txIndex,
|
||||
BlockHash: common.Hash{},
|
||||
Index: 0,
|
||||
Removed: false,
|
||||
}
|
||||
mockFetcher.SetReturnLogs([]types.Log{fakeLog})
|
||||
transformer := price_feeds.PriceFeedTransformer{
|
||||
Fetcher: mockFetcher,
|
||||
Repository: mockRepository,
|
||||
}
|
||||
|
||||
err := transformer.Execute()
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedModel := price_feeds.PriceFeedModel{
|
||||
BlockNumber: blockNumber,
|
||||
HeaderID: headerID,
|
||||
MedianizerAddress: address.Bytes(),
|
||||
UsdValue: fmt.Sprintf("%d", usdValue),
|
||||
TransactionIndex: txIndex,
|
||||
}
|
||||
mockRepository.AssertCreateCalledWith(expectedModel)
|
||||
})
|
||||
|
||||
It("returns error if creating price feed update returns error", func() {
|
||||
mockRepository := &price_feeds2.MockPriceFeedRepository{}
|
||||
mockRepository.SetMissingHeaders([]core.Header{{BlockNumber: 1, Id: 2}})
|
||||
mockRepository.SetCreateErr(fakes.FakeError)
|
||||
mockFetcher := &price_feeds2.MockPriceFeedFetcher{}
|
||||
mockFetcher.SetReturnLogs([]types.Log{{}})
|
||||
transformer := price_feeds.PriceFeedTransformer{
|
||||
Fetcher: mockFetcher,
|
||||
Repository: mockRepository,
|
||||
}
|
||||
|
||||
err := transformer.Execute()
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user