diff --git a/db/migrations/1535667935_create_pit_file_table.down.sql b/db/migrations/1535667935_create_pit_file_table.down.sql index 0b8b947de..f08e09138 100644 --- a/db/migrations/1535667935_create_pit_file_table.down.sql +++ b/db/migrations/1535667935_create_pit_file_table.down.sql @@ -1,2 +1,3 @@ DROP TABLE maker.pit_file_ilk; -DROP TABLE maker.pit_file_stability_fee; \ No newline at end of file +DROP TABLE maker.pit_file_stability_fee; +DROP TABLE maker.pit_file_debt_ceiling; \ No newline at end of file diff --git a/db/migrations/1535667935_create_pit_file_table.up.sql b/db/migrations/1535667935_create_pit_file_table.up.sql index 8c0dd5ae7..a86fd6819 100644 --- a/db/migrations/1535667935_create_pit_file_table.up.sql +++ b/db/migrations/1535667935_create_pit_file_table.up.sql @@ -17,4 +17,14 @@ CREATE TABLE maker.pit_file_stability_fee ( tx_idx INTEGER NOT NULL, raw_log JSONB, UNIQUE (header_id, tx_idx) -) \ No newline at end of file +); + +CREATE TABLE maker.pit_file_debt_ceiling ( + id SERIAL PRIMARY KEY, + header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, + what TEXT, + data NUMERIC, + tx_idx INTEGER NOT NULL, + raw_log JSONB, + UNIQUE (header_id, tx_idx) +); \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index a84f76afb..739b9e1fe 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -156,6 +156,40 @@ CREATE SEQUENCE maker.frob_id_seq ALTER SEQUENCE maker.frob_id_seq OWNED BY maker.frob.id; +-- +-- Name: pit_file_debt_ceiling; Type: TABLE; Schema: maker; Owner: - +-- + +CREATE TABLE maker.pit_file_debt_ceiling ( + id integer NOT NULL, + header_id integer NOT NULL, + what text, + data numeric, + tx_idx integer NOT NULL, + raw_log jsonb +); + + +-- +-- Name: pit_file_debt_ceiling_id_seq; Type: SEQUENCE; Schema: maker; Owner: - +-- + +CREATE SEQUENCE maker.pit_file_debt_ceiling_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: pit_file_debt_ceiling_id_seq; Type: SEQUENCE OWNED BY; Schema: maker; Owner: - +-- + +ALTER SEQUENCE maker.pit_file_debt_ceiling_id_seq OWNED BY maker.pit_file_debt_ceiling.id; + + -- -- Name: pit_file_ilk; Type: TABLE; Schema: maker; Owner: - -- @@ -689,6 +723,13 @@ ALTER TABLE ONLY maker.flip_kick ALTER COLUMN db_id SET DEFAULT nextval('maker.f ALTER TABLE ONLY maker.frob ALTER COLUMN id SET DEFAULT nextval('maker.frob_id_seq'::regclass); +-- +-- Name: pit_file_debt_ceiling id; Type: DEFAULT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.pit_file_debt_ceiling ALTER COLUMN id SET DEFAULT nextval('maker.pit_file_debt_ceiling_id_seq'::regclass); + + -- -- Name: pit_file_ilk id; Type: DEFAULT; Schema: maker; Owner: - -- @@ -828,6 +869,22 @@ ALTER TABLE ONLY maker.frob ADD CONSTRAINT frob_pkey PRIMARY KEY (id); +-- +-- Name: pit_file_debt_ceiling pit_file_debt_ceiling_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.pit_file_debt_ceiling + ADD CONSTRAINT pit_file_debt_ceiling_header_id_tx_idx_key UNIQUE (header_id, tx_idx); + + +-- +-- Name: pit_file_debt_ceiling pit_file_debt_ceiling_pkey; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.pit_file_debt_ceiling + ADD CONSTRAINT pit_file_debt_ceiling_pkey PRIMARY KEY (id); + + -- -- Name: pit_file_ilk pit_file_ilk_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: - -- @@ -1055,6 +1112,14 @@ ALTER TABLE ONLY maker.price_feeds ADD CONSTRAINT headers_fk FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; +-- +-- Name: pit_file_debt_ceiling pit_file_debt_ceiling_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.pit_file_debt_ceiling + ADD CONSTRAINT pit_file_debt_ceiling_header_id_fkey FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; + + -- -- Name: pit_file_ilk pit_file_ilk_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - -- diff --git a/pkg/transformers/pit_file/debt_ceiling/converter.go b/pkg/transformers/pit_file/debt_ceiling/converter.go new file mode 100644 index 000000000..fbc78ed8a --- /dev/null +++ b/pkg/transformers/pit_file/debt_ceiling/converter.go @@ -0,0 +1,29 @@ +package debt_ceiling + +import ( + "encoding/json" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "math/big" +) + +type Converter interface { + ToModel(contractAddress string, contractAbi string, ethLog types.Log) (PitFileDebtCeilingModel, error) +} + +type PitFileDebtCeilingConverter struct{} + +func (PitFileDebtCeilingConverter) ToModel(contractAddress string, contractAbi string, ethLog types.Log) (PitFileDebtCeilingModel, error) { + what := common.HexToAddress(ethLog.Topics[1].String()).String() + itemByteLength := 32 + riskBytes := ethLog.Data[len(ethLog.Data)-itemByteLength:] + data := big.NewInt(0).SetBytes(riskBytes).String() + + raw, err := json.Marshal(ethLog) + return PitFileDebtCeilingModel{ + What: what, + Data: data, + TransactionIndex: ethLog.TxIndex, + Raw: raw, + }, err +} diff --git a/pkg/transformers/pit_file/debt_ceiling/converter_test.go b/pkg/transformers/pit_file/debt_ceiling/converter_test.go new file mode 100644 index 000000000..c8975d4ea --- /dev/null +++ b/pkg/transformers/pit_file/debt_ceiling/converter_test.go @@ -0,0 +1,25 @@ +package debt_ceiling_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/debt_ceiling" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" +) + +var _ = Describe("", func() { + It("converts a log to an model", func() { + converter := debt_ceiling.PitFileDebtCeilingConverter{} + + model, err := converter.ToModel(test_data.PitAddress, shared.PitABI, test_data.EthPitFileDebtCeilingLog) + + Expect(err).NotTo(HaveOccurred()) + Expect(model.What).To(Equal(test_data.PitFileDebtCeilingModel.What)) + Expect(model.Data).To(Equal(test_data.PitFileDebtCeilingModel.Data)) + Expect(model.TransactionIndex).To(Equal(test_data.PitFileDebtCeilingModel.TransactionIndex)) + Expect(model.Raw).To(Equal(test_data.PitFileDebtCeilingModel.Raw)) + Expect(model).To(Equal(test_data.PitFileDebtCeilingModel)) + }) +}) diff --git a/pkg/transformers/pit_file/debt_ceiling/model.go b/pkg/transformers/pit_file/debt_ceiling/model.go new file mode 100644 index 000000000..f1ff45f4d --- /dev/null +++ b/pkg/transformers/pit_file/debt_ceiling/model.go @@ -0,0 +1,8 @@ +package debt_ceiling + +type PitFileDebtCeilingModel struct { + What string + Data string + TransactionIndex uint `db:"tx_idx"` + Raw []byte `db:"raw_log"` +} diff --git a/pkg/transformers/pit_file/debt_ceiling/repository.go b/pkg/transformers/pit_file/debt_ceiling/repository.go new file mode 100644 index 000000000..4fde4c5b6 --- /dev/null +++ b/pkg/transformers/pit_file/debt_ceiling/repository.go @@ -0,0 +1,48 @@ +package debt_ceiling + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +type Repository interface { + Create(headerID int64, model PitFileDebtCeilingModel) error + MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) +} + +type PitFileDebtCeilingRepository struct { + db *postgres.DB +} + +func NewPitFileDebtCeilingRepository(db *postgres.DB) PitFileDebtCeilingRepository { + return PitFileDebtCeilingRepository{ + db: db, + } +} + +func (repository PitFileDebtCeilingRepository) Create(headerID int64, model PitFileDebtCeilingModel) error { + _, err := repository.db.Exec( + `INSERT into maker.pit_file_debt_ceiling (header_id, what, data, tx_idx, raw_log) + VALUES($1, $2, $3::NUMERIC, $4, $5)`, + headerID, model.What, model.Data, model.TransactionIndex, model.Raw, + ) + return err +} + +func (repository PitFileDebtCeilingRepository) 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.pit_file_debt_ceiling 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 +} diff --git a/pkg/transformers/pit_file/debt_ceiling/repository_test.go b/pkg/transformers/pit_file/debt_ceiling/repository_test.go new file mode 100644 index 000000000..60df3c915 --- /dev/null +++ b/pkg/transformers/pit_file/debt_ceiling/repository_test.go @@ -0,0 +1,130 @@ +package debt_ceiling_test + +import ( + "database/sql" + + . "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/pit_file/debt_ceiling" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("", func() { + Describe("Create", func() { + It("adds a pit file debt ceiling event", func() { + db := test_config.NewTestDB(core.Node{}) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + pitFileRepository := debt_ceiling.NewPitFileDebtCeilingRepository(db) + + err = pitFileRepository.Create(headerID, test_data.PitFileDebtCeilingModel) + + Expect(err).NotTo(HaveOccurred()) + var dbPitFile debt_ceiling.PitFileDebtCeilingModel + err = db.Get(&dbPitFile, `SELECT what, data, tx_idx, raw_log FROM maker.pit_file_debt_ceiling WHERE header_id = $1`, headerID) + Expect(err).NotTo(HaveOccurred()) + Expect(dbPitFile.What).To(Equal(test_data.PitFileDebtCeilingModel.What)) + Expect(dbPitFile.Data).To(Equal(test_data.PitFileDebtCeilingModel.Data)) + Expect(dbPitFile.TransactionIndex).To(Equal(test_data.PitFileDebtCeilingModel.TransactionIndex)) + Expect(dbPitFile.Raw).To(MatchJSON(test_data.PitFileDebtCeilingModel.Raw)) + }) + + It("does not duplicate pit file events", func() { + db := test_config.NewTestDB(core.Node{}) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + pitFileRepository := debt_ceiling.NewPitFileDebtCeilingRepository(db) + err = pitFileRepository.Create(headerID, test_data.PitFileDebtCeilingModel) + Expect(err).NotTo(HaveOccurred()) + + err = pitFileRepository.Create(headerID, test_data.PitFileDebtCeilingModel) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("pq: duplicate key value violates unique constraint")) + }) + + It("removes pit file if corresponding header is deleted", func() { + db := test_config.NewTestDB(core.Node{}) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + pitFileRepository := debt_ceiling.NewPitFileDebtCeilingRepository(db) + err = pitFileRepository.Create(headerID, test_data.PitFileDebtCeilingModel) + Expect(err).NotTo(HaveOccurred()) + + _, err = db.Exec(`DELETE FROM headers WHERE id = $1`, headerID) + + Expect(err).NotTo(HaveOccurred()) + var dbPitFile debt_ceiling.PitFileDebtCeilingModel + err = db.Get(&dbPitFile, `SELECT what, data, tx_idx, raw_log FROM maker.pit_file_debt_ceiling WHERE header_id = $1`, headerID) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(sql.ErrNoRows)) + }) + }) + + Describe("MissingHeaders", func() { + It("returns headers with no associated pit file event", func() { + db := test_config.NewTestDB(core.Node{}) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + startingBlockNumber := int64(1) + pitFileBlockNumber := int64(2) + endingBlockNumber := int64(3) + blockNumbers := []int64{startingBlockNumber, pitFileBlockNumber, 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()) + } + pitFileRepository := debt_ceiling.NewPitFileDebtCeilingRepository(db) + err := pitFileRepository.Create(headerIDs[1], test_data.PitFileDebtCeilingModel) + Expect(err).NotTo(HaveOccurred()) + + headers, err := pitFileRepository.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() { + db := test_config.NewTestDB(core.Node{}) + test_config.CleanTestDB(db) + blockNumbers := []int64{1, 2, 3} + headerRepository := repositories.NewHeaderRepository(db) + dbTwo := test_config.NewTestDB(core.Node{ID: "second"}) + 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()) + } + pitFileRepository := debt_ceiling.NewPitFileDebtCeilingRepository(db) + pitFileRepositoryTwo := debt_ceiling.NewPitFileDebtCeilingRepository(dbTwo) + err := pitFileRepository.Create(headerIDs[0], test_data.PitFileDebtCeilingModel) + Expect(err).NotTo(HaveOccurred()) + + nodeOneMissingHeaders, err := pitFileRepository.MissingHeaders(blockNumbers[0], blockNumbers[len(blockNumbers)-1]) + Expect(err).NotTo(HaveOccurred()) + Expect(len(nodeOneMissingHeaders)).To(Equal(len(blockNumbers) - 1)) + + nodeTwoMissingHeaders, err := pitFileRepositoryTwo.MissingHeaders(blockNumbers[0], blockNumbers[len(blockNumbers)-1]) + Expect(err).NotTo(HaveOccurred()) + Expect(len(nodeTwoMissingHeaders)).To(Equal(len(blockNumbers))) + }) + }) +}) diff --git a/pkg/transformers/pit_file/debt_ceiling/transformer.go b/pkg/transformers/pit_file/debt_ceiling/transformer.go new file mode 100644 index 000000000..9d7bddb94 --- /dev/null +++ b/pkg/transformers/pit_file/debt_ceiling/transformer.go @@ -0,0 +1,57 @@ +package debt_ceiling + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" +) + +type PitFileDebtCeilingTransformer struct { + Config shared.TransformerConfig + Converter Converter + Fetcher shared.LogFetcher + Repository Repository +} + +type PitFileDebtCeilingTransformerInitializer struct { + Config shared.TransformerConfig +} + +func (initializer PitFileDebtCeilingTransformerInitializer) NewPitFileDebtCeilingTransformer(db *postgres.DB, blockChain core.BlockChain) shared.Transformer { + converter := PitFileDebtCeilingConverter{} + fetcher := shared.NewFetcher(blockChain) + repository := NewPitFileDebtCeilingRepository(db) + return PitFileDebtCeilingTransformer{ + Config: initializer.Config, + Converter: converter, + Fetcher: fetcher, + Repository: repository, + } +} + +func (transformer PitFileDebtCeilingTransformer) Execute() error { + missingHeaders, err := transformer.Repository.MissingHeaders(transformer.Config.StartingBlockNumber, transformer.Config.EndingBlockNumber) + if err != nil { + return err + } + for _, header := range missingHeaders { + topics := [][]common.Hash{{common.HexToHash(shared.PitFileDebtCeilingSignature)}} + matchingLogs, err := transformer.Fetcher.FetchLogs(pit_file.PitFileConfig.ContractAddress, topics, header.BlockNumber) + if err != nil { + return err + } + for _, log := range matchingLogs { + model, err := transformer.Converter.ToModel(pit_file.PitFileConfig.ContractAddress, shared.PitABI, log) + if err != nil { + return err + } + err = transformer.Repository.Create(header.Id, model) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/transformers/pit_file/debt_ceiling/transformer_test.go b/pkg/transformers/pit_file/debt_ceiling/transformer_test.go new file mode 100644 index 000000000..cfd8006b2 --- /dev/null +++ b/pkg/transformers/pit_file/debt_ceiling/transformer_test.go @@ -0,0 +1,162 @@ +package debt_ceiling_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/core" + "github.com/vulcanize/vulcanizedb/pkg/fakes" + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file" + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/debt_ceiling" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks" + debt_ceiling_mocks "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks/pit_file/debt_ceiling" +) + +var _ = Describe("", func() { + It("gets missing headers for block numbers specified in config", func() { + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Config: pit_file.PitFileConfig, + Fetcher: &mocks.MockLogFetcher{}, + Converter: &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(repository.PassedStartingBlockNumber).To(Equal(pit_file.PitFileConfig.StartingBlockNumber)) + Expect(repository.PassedEndingBlockNumber).To(Equal(pit_file.PitFileConfig.EndingBlockNumber)) + }) + + It("returns error if repository returns error for missing headers", func() { + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + repository.SetMissingHeadersErr(fakes.FakeError) + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Fetcher: &mocks.MockLogFetcher{}, + Converter: &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("fetches logs for missing headers", func() { + fetcher := &mocks.MockLogFetcher{} + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}, {BlockNumber: 2}}) + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Fetcher: fetcher, + Converter: &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(fetcher.FetchedBlocks).To(Equal([]int64{1, 2})) + Expect(fetcher.FetchedContractAddress).To(Equal(pit_file.PitFileConfig.ContractAddress)) + Expect(fetcher.FetchedTopics).To(Equal([][]common.Hash{{common.HexToHash(shared.PitFileDebtCeilingSignature)}})) + }) + + It("returns error if fetcher returns error", func() { + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetcherError(fakes.FakeError) + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Fetcher: fetcher, + Converter: &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("converts matching logs", func() { + converter := &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{} + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthPitFileDebtCeilingLog}) + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(converter.PassedContractAddress).To(Equal(pit_file.PitFileConfig.ContractAddress)) + Expect(converter.PassedContractABI).To(Equal(pit_file.PitFileConfig.ContractAbi)) + Expect(converter.PassedLog).To(Equal(test_data.EthPitFileDebtCeilingLog)) + }) + + It("returns error if converter returns error", func() { + converter := &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{} + converter.SetConverterError(fakes.FakeError) + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthPitFileDebtCeilingLog}) + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("persists pit file model", func() { + converter := &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{} + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthPitFileDebtCeilingLog}) + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + fakeHeader := core.Header{BlockNumber: 1, Id: 2} + repository.SetMissingHeaders([]core.Header{fakeHeader}) + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(repository.PassedHeaderID).To(Equal(fakeHeader.Id)) + Expect(repository.PassedModel).To(Equal(test_data.PitFileDebtCeilingModel)) + }) + + It("returns error if repository returns error for create", func() { + converter := &debt_ceiling_mocks.MockPitFileDebtCeilingConverter{} + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthPitFileDebtCeilingLog}) + repository := &debt_ceiling_mocks.MockPitFileDebtCeilingRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1, Id: 2}}) + repository.SetCreateError(fakes.FakeError) + transformer := debt_ceiling.PitFileDebtCeilingTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) +}) diff --git a/pkg/transformers/shared/constants.go b/pkg/transformers/shared/constants.go index 38bfb0108..41f716435 100644 --- a/pkg/transformers/shared/constants.go +++ b/pkg/transformers/shared/constants.go @@ -18,9 +18,9 @@ var ( biteMethod = "Bite(bytes32,bytes32,uint256,uint256,uint256,uint256,uint256)" flipKickMethod = "Kick(uint256,uint256,uint256,address,uint48,bytes32,uint256)" frobMethod = "Frob(bytes32,bytes32,uint256,uint256,int256,int256,uint256)" + pitFileDebtCeilingMethod = "file(bytes32,uint256)" pitFileIlkMethod = "file(bytes32,bytes32,uint256)" pitFileStabilityFeeMethod = "file(bytes32,address)" - pitFileDebtCeilingMethod = "file(bytes32,uint256)" tendMethod = "tend(uint256,uint256,uint256)" CatABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"vat\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"vow\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"flips\",\"outputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"lad\",\"type\":\"bytes32\"},{\"name\":\"ink\",\"type\":\"uint256\"},{\"name\":\"tab\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"nflip\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"live\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"wards\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"ilks\",\"outputs\":[{\"name\":\"flip\",\"type\":\"address\"},{\"name\":\"chop\",\"type\":\"uint256\"},{\"name\":\"lump\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"pit\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"vat_\",\"type\":\"address\"},{\"name\":\"pit_\",\"type\":\"address\"},{\"name\":\"vow_\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"ilk\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"lad\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"ink\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"art\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"tab\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"flip\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"iArt\",\"type\":\"uint256\"}],\"name\":\"Bite\",\"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\"},{\"constant\":false,\"inputs\":[{\"name\":\"guy\",\"type\":\"address\"}],\"name\":\"rely\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"guy\",\"type\":\"address\"}],\"name\":\"deny\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"what\",\"type\":\"bytes32\"},{\"name\":\"data\",\"type\":\"uint256\"}],\"name\":\"file\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"what\",\"type\":\"bytes32\"},{\"name\":\"flip\",\"type\":\"address\"}],\"name\":\"file\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"lad\",\"type\":\"bytes32\"}],\"name\":\"bite\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"n\",\"type\":\"uint256\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"flip\",\"outputs\":[{\"name\":\"id\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" @@ -34,8 +34,8 @@ var ( FlipKickSignature = GetEventSignature(flipKickMethod) FrobSignature = GetEventSignature(frobMethod) LogValueSignature = GetEventSignature("") + PitFileDebtCeilingSignature = GetLogNoteSignature(pitFileDebtCeilingMethod) PitFileIlkSignature = GetLogNoteSignature(pitFileIlkMethod) PitFileStabilityFeeSignature = GetLogNoteSignature(pitFileStabilityFeeMethod) - PitFileDebtCeilingSignature = GetLogNoteSignature(pitFileDebtCeilingMethod) TendFunctionSignature = GetLogNoteSignature(tendMethod) ) diff --git a/pkg/transformers/test_data/mocks/pit_file/debt_ceiling/converter.go b/pkg/transformers/test_data/mocks/pit_file/debt_ceiling/converter.go new file mode 100644 index 000000000..5ba4bb43d --- /dev/null +++ b/pkg/transformers/test_data/mocks/pit_file/debt_ceiling/converter.go @@ -0,0 +1,25 @@ +package debt_ceiling + +import ( + "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/debt_ceiling" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" +) + +type MockPitFileDebtCeilingConverter struct { + converterErr error + PassedContractAddress string + PassedContractABI string + PassedLog types.Log +} + +func (converter *MockPitFileDebtCeilingConverter) ToModel(contractAddress string, contractAbi string, ethLog types.Log) (debt_ceiling.PitFileDebtCeilingModel, error) { + converter.PassedContractAddress = contractAddress + converter.PassedContractABI = contractAbi + converter.PassedLog = ethLog + return test_data.PitFileDebtCeilingModel, converter.converterErr +} + +func (converter *MockPitFileDebtCeilingConverter) SetConverterError(e error) { + converter.converterErr = e +} diff --git a/pkg/transformers/test_data/mocks/pit_file/debt_ceiling/repository.go b/pkg/transformers/test_data/mocks/pit_file/debt_ceiling/repository.go new file mode 100644 index 000000000..2ad41b168 --- /dev/null +++ b/pkg/transformers/test_data/mocks/pit_file/debt_ceiling/repository.go @@ -0,0 +1,40 @@ +package debt_ceiling + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/debt_ceiling" +) + +type MockPitFileDebtCeilingRepository struct { + createErr error + missingHeaders []core.Header + missingHeadersErr error + PassedStartingBlockNumber int64 + PassedEndingBlockNumber int64 + PassedHeaderID int64 + PassedModel debt_ceiling.PitFileDebtCeilingModel +} + +func (repository *MockPitFileDebtCeilingRepository) Create(headerID int64, model debt_ceiling.PitFileDebtCeilingModel) error { + repository.PassedHeaderID = headerID + repository.PassedModel = model + return repository.createErr +} + +func (repository *MockPitFileDebtCeilingRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) { + repository.PassedStartingBlockNumber = startingBlockNumber + repository.PassedEndingBlockNumber = endingBlockNumber + return repository.missingHeaders, repository.missingHeadersErr +} + +func (repository *MockPitFileDebtCeilingRepository) SetMissingHeadersErr(e error) { + repository.missingHeadersErr = e +} + +func (repository *MockPitFileDebtCeilingRepository) SetMissingHeaders(headers []core.Header) { + repository.missingHeaders = headers +} + +func (repository *MockPitFileDebtCeilingRepository) SetCreateError(e error) { + repository.createErr = e +} diff --git a/pkg/transformers/test_data/pit_file.go b/pkg/transformers/test_data/pit_file.go index 664e34521..d5e783809 100644 --- a/pkg/transformers/test_data/pit_file.go +++ b/pkg/transformers/test_data/pit_file.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/debt_ceiling" ilk2 "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/ilk" "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/stability_fee" "math/big" @@ -14,6 +15,31 @@ var ( PitAddress = "0xff3f2400f1600f3f493a9a92704a29b96795af1a" ) +var EthPitFileDebtCeilingLog = types.Log{ + Address: common.HexToAddress(PitAddress), + Topics: []common.Hash{ + common.HexToHash("0x29ae811400000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0x00000000000000000000000064d922894153be9eef7b7218dc565d1d0ce2a092"), + common.HexToHash("0x6472697000000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000007b"), + }, + Data: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004429ae81146472697000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b"), + BlockNumber: 22, + TxHash: common.HexToHash("0xd744878a0b6655e3ba729e1019f56b563b4a16750196b8ad6104f3977db43f42"), + TxIndex: 333, + BlockHash: common.HexToHash("0xa54d9d99c315bea3dda7256a36e51773ed009a01c0859295c5382d4b83d7eeb9"), + Index: 0, + Removed: false, +} + +var rawPitFileDebtCeilingLog, _ = json.Marshal(EthPitFileDebtCeilingLog) +var PitFileDebtCeilingModel = debt_ceiling.PitFileDebtCeilingModel{ + What: "0x64d922894153BE9EEf7b7218dc565d1D0Ce2a092", + Data: big.NewInt(123).String(), + TransactionIndex: EthPitFileDebtCeilingLog.TxIndex, + Raw: rawPitFileDebtCeilingLog, +} + var EthPitFileIlkLog = types.Log{ Address: common.HexToAddress(PitAddress), Topics: []common.Hash{ @@ -51,7 +77,7 @@ var EthPitFileStabilityFeeLog = types.Log{ Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044d4e8be8364726970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), BlockNumber: 12, TxHash: common.HexToHash("0x78cdc62316ccf8e31515d09745cc724f557569f01a557d0d09b1066bf7079fd2"), - TxIndex: 0, + TxIndex: 222, BlockHash: common.HexToHash("0xe3d8e458421533170871b4033f978a3793ef10b7e33a9328a13c09e2fd90208d"), Index: 0, Removed: false, @@ -61,6 +87,6 @@ var rawPitFileStabilityFeeLog, _ = json.Marshal(EthPitFileStabilityFeeLog) var PitFileStabilityFeeModel = stability_fee.PitFileStabilityFeeModel{ What: "drip", Data: "0x64d922894153BE9EEf7b7218dc565d1D0Ce2a092", - TransactionIndex: 0, + TransactionIndex: EthPitFileStabilityFeeLog.TxIndex, Raw: rawPitFileStabilityFeeLog, } diff --git a/pkg/transformers/transformers.go b/pkg/transformers/transformers.go index a83a7e159..26fe9f789 100644 --- a/pkg/transformers/transformers.go +++ b/pkg/transformers/transformers.go @@ -19,6 +19,7 @@ import ( "github.com/vulcanize/vulcanizedb/pkg/transformers/flip_kick" "github.com/vulcanize/vulcanizedb/pkg/transformers/frob" "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file" + "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/debt_ceiling" "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/ilk" "github.com/vulcanize/vulcanizedb/pkg/transformers/pit_file/stability_fee" "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds" @@ -32,6 +33,7 @@ func TransformerInitializers() []shared.TransformerInitializer { frobConfig := frob.FrobConfig frobTransformerInitializer := frob.FrobTransformerInitializer{Config: frobConfig} pitFileConfig := pit_file.PitFileConfig + pitFileDebtCeilingTransformerInitializer := debt_ceiling.PitFileDebtCeilingTransformerInitializer{Config: pitFileConfig} pitFileIlkTransformerInitializer := ilk.PitFileIlkTransformerInitializer{Config: pitFileConfig} pitFileStabilityFeeTransformerInitializer := stability_fee.PitFileStabilityFeeTransformerInitializer{Config: pitFileConfig} priceFeedConfig := price_feeds.PriceFeedConfig @@ -44,6 +46,7 @@ func TransformerInitializers() []shared.TransformerInitializer { biteTransformerInitializer.NewBiteTransformer, flipKickTransformerInitializer.NewFlipKickTransformer, frobTransformerInitializer.NewFrobTransformer, + pitFileDebtCeilingTransformerInitializer.NewPitFileDebtCeilingTransformer, pitFileIlkTransformerInitializer.NewPitFileIlkTransformer, pitFileStabilityFeeTransformerInitializer.NewPitFileStabilityFeeTransformer, priceFeedTransformerInitializer.NewPriceFeedTransformer,