diff --git a/README.md b/README.md index d00eccdc..bf586a36 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ Vulcanize DB is a set of tools that make it easier for developers to write appli * See below for configuring additional environments +## Create a migration file (up and down) +1. ./script/create_migrate create_bite_table + ## Configuration - To use a local Ethereum node, copy `environments/public.toml.example` to `environments/public.toml` and update the `ipcPath` and `levelDbPath`. diff --git a/db/migrations/1534295713_create_bite_table.down.sql b/db/migrations/1534295713_create_bite_table.down.sql new file mode 100644 index 00000000..1ca07801 --- /dev/null +++ b/db/migrations/1534295713_create_bite_table.down.sql @@ -0,0 +1 @@ +DROP TABLE maker.bite; diff --git a/db/migrations/1534295713_create_bite_table.up.sql b/db/migrations/1534295713_create_bite_table.up.sql new file mode 100644 index 00000000..5cc7041f --- /dev/null +++ b/db/migrations/1534295713_create_bite_table.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE maker.bite ( + id SERIAL PRIMARY KEY, + header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, + ilk bytea, + lad bytea, + ink VARCHAR, + art VARCHAR, + iArt VARCHAR, + tab NUMERIC, + flip VARCHAR, + 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 58a0dba5..dadb9048 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -40,6 +40,45 @@ SET default_tablespace = ''; SET default_with_oids = false; +-- +-- Name: bite; Type: TABLE; Schema: maker; Owner: - +-- + +CREATE TABLE maker.bite ( + id integer NOT NULL, + header_id integer NOT NULL, + ilk bytea, + lad bytea, + ink character varying, + art character varying, + iart character varying, + tab numeric, + flip character varying, + tx_idx integer NOT NULL, + raw_log jsonb +); + + +-- +-- Name: bite_id_seq; Type: SEQUENCE; Schema: maker; Owner: - +-- + +CREATE SEQUENCE maker.bite_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: bite_id_seq; Type: SEQUENCE OWNED BY; Schema: maker; Owner: - +-- + +ALTER SEQUENCE maker.bite_id_seq OWNED BY maker.bite.id; + + -- -- Name: flip_kick; Type: TABLE; Schema: maker; Owner: - -- @@ -564,6 +603,13 @@ CREATE VIEW public.watched_event_logs AS WHERE ((((log_filters.topic0)::text = (logs.topic0)::text) OR (log_filters.topic0 IS NULL)) AND (((log_filters.topic1)::text = (logs.topic1)::text) OR (log_filters.topic1 IS NULL)) AND (((log_filters.topic2)::text = (logs.topic2)::text) OR (log_filters.topic2 IS NULL)) AND (((log_filters.topic3)::text = (logs.topic3)::text) OR (log_filters.topic3 IS NULL))); +-- +-- Name: bite id; Type: DEFAULT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.bite ALTER COLUMN id SET DEFAULT nextval('maker.bite_id_seq'::regclass); + + -- -- Name: flip_kick db_id; Type: DEFAULT; Schema: maker; Owner: - -- @@ -655,6 +701,22 @@ ALTER TABLE ONLY public.transactions ALTER COLUMN id SET DEFAULT nextval('public ALTER TABLE ONLY public.watched_contracts ALTER COLUMN contract_id SET DEFAULT nextval('public.watched_contracts_contract_id_seq'::regclass); +-- +-- Name: bite bite_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.bite + ADD CONSTRAINT bite_header_id_tx_idx_key UNIQUE (header_id, tx_idx); + + +-- +-- Name: bite bite_pkey; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.bite + ADD CONSTRAINT bite_pkey PRIMARY KEY (id); + + -- -- Name: flip_kick flip_kick_id_key; Type: CONSTRAINT; Schema: maker; Owner: - -- @@ -850,6 +912,14 @@ CREATE INDEX tx_from_index ON public.transactions USING btree (tx_from); CREATE INDEX tx_to_index ON public.transactions USING btree (tx_to); +-- +-- Name: bite bite_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.bite + ADD CONSTRAINT bite_header_id_fkey FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; + + -- -- Name: flip_kick flip_kick_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - -- diff --git a/pkg/transformers/bite/bite_suite_test.go b/pkg/transformers/bite/bite_suite_test.go new file mode 100644 index 00000000..ab9920d3 --- /dev/null +++ b/pkg/transformers/bite/bite_suite_test.go @@ -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 bite_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestBite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Bite Suite") +} diff --git a/pkg/transformers/bite/config.go b/pkg/transformers/bite/config.go new file mode 100644 index 00000000..6c743cf5 --- /dev/null +++ b/pkg/transformers/bite/config.go @@ -0,0 +1,29 @@ +/* + * 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 bite + +import ( + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" +) + +var BiteConfig = shared.TransformerConfig{ + ContractAddresses: "0xe0f0fa6982c59d8aa4ae0134bfe048327bd788cacf758b643ca41f055ffce76c", //this is a temporary address deployed locally + ContractAbi: BiteABI, + Topics: []string{BiteSignature}, + StartingBlockNumber: 0, + EndingBlockNumber: 100, +} diff --git a/pkg/transformers/bite/constants.go b/pkg/transformers/bite/constants.go new file mode 100644 index 00000000..66f3ee3f --- /dev/null +++ b/pkg/transformers/bite/constants.go @@ -0,0 +1,22 @@ +/* + * 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 bite + +var ( + BiteABI = "[{\"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\"}]" + BiteSignature = "0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8" +) diff --git a/pkg/transformers/bite/converter.go b/pkg/transformers/bite/converter.go new file mode 100644 index 00000000..558a5553 --- /dev/null +++ b/pkg/transformers/bite/converter.go @@ -0,0 +1,84 @@ +/* + * 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 bite + +import ( + "encoding/json" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/transformers/utilities" +) + +type Converter interface { + ToEntity(contractAddress string, contractAbi string, ethLog types.Log) (BiteEntity, error) + ToModel(flipKick BiteEntity) (BiteModel, error) +} + +type BiteConverter struct{} + +func (BiteConverter) ToEntity(contractAddress string, contractAbi string, ethLog types.Log) (BiteEntity, error) { + entity := BiteEntity{} + address := common.HexToAddress(contractAddress) + abi, err := geth.ParseAbi(contractAbi) + if err != nil { + return entity, err + } + + contract := bind.NewBoundContract(address, abi, nil, nil, nil) + + err = contract.UnpackLog(&entity, "Bite", ethLog) + if err != nil { + return entity, err + } + + entity.Raw = ethLog + entity.TransactionIndex = ethLog.TxIndex + + return entity, nil +} +func (converter BiteConverter) ToModel(entity BiteEntity) (BiteModel, error) { + + id := entity.Id.String() + ilk := entity.Ilk[:] + lad := entity.Lad[:] + ink := entity.Ink.String() + art := entity.Art.String() + iArt := entity.IArt.String() + tab := entity.Tab.String() + flip := entity.Flip.String() + txIdx := entity.TransactionIndex + rawLogJson, err := json.Marshal(entity.Raw) + rawLogString := string(rawLogJson) + if err != nil { + return BiteModel{}, err + } + + return BiteModel{ + Id: utilities.ConvertNilToEmptyString(id), + Ilk: ilk, + Lad: lad, + Ink: utilities.ConvertNilToEmptyString(ink), + Art: utilities.ConvertNilToEmptyString(art), + IArt: utilities.ConvertNilToEmptyString(iArt), + Tab: utilities.ConvertNilToEmptyString(tab), + Flip: utilities.ConvertNilToEmptyString(flip), + TransactionIndex: txIdx, + Raw: rawLogString, + }, nil +} diff --git a/pkg/transformers/bite/converter_test.go b/pkg/transformers/bite/converter_test.go new file mode 100644 index 00000000..f7f48137 --- /dev/null +++ b/pkg/transformers/bite/converter_test.go @@ -0,0 +1,93 @@ +/* + * 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 bite_test + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/transformers/bite" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Bite Converter", func() { + var converter = bite.BiteConverter{} + + Describe("ToEntity", func() { + It("converts an eth log to a bite entity", func() { + entity, err := converter.ToEntity(test_data.TemporaryBiteAddress, bite.BiteABI, test_data.EthBiteLog) + + Expect(err).NotTo(HaveOccurred()) + Expect(entity.Ilk).To(Equal(test_data.BiteEntity.Ilk)) + Expect(entity.Lad).To(Equal(test_data.BiteEntity.Lad)) + Expect(entity.Ink).To(Equal(test_data.BiteEntity.Ink)) + Expect(entity.Art).To(Equal(test_data.BiteEntity.Art)) + Expect(entity.Tab).To(Equal(test_data.BiteEntity.Tab)) + Expect(entity.Flip).To(Equal(test_data.BiteEntity.Flip)) + Expect(entity.IArt).To(Equal(test_data.BiteEntity.IArt)) + Expect(entity.TransactionIndex).To(Equal(test_data.BiteEntity.TransactionIndex)) + Expect(entity.Raw).To(Equal(test_data.BiteEntity.Raw)) + }) + + It("returns an error if converting log to entity fails", func() { + _, err := converter.ToEntity(test_data.TemporaryBiteAddress, "error abi", test_data.EthBiteLog) + + Expect(err).To(HaveOccurred()) + }) + }) + + Describe("ToModel", func() { + var emptyEntity = bite.BiteEntity{} + + BeforeEach(func() { + emptyEntity.Id = big.NewInt(1) + }) + + It("converts an Entity to a Model", func() { + model, err := converter.ToModel(test_data.BiteEntity) + + Expect(err).NotTo(HaveOccurred()) + Expect(model).To(Equal(test_data.BiteModel)) + Expect(model.TransactionIndex).To(Equal(test_data.BiteModel.TransactionIndex)) + }) + + It("handles nil values", func() { + emptyLog, err := json.Marshal(types.Log{}) + Expect(err).NotTo(HaveOccurred()) + expectedModel := bite.BiteModel{ + Id: "1", + Ilk: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Lad: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Ink: "", + Art: "", + IArt: "", + Tab: "", + Flip: "", + TransactionIndex: 0, + Raw: string(emptyLog), + } + model, err := converter.ToModel(emptyEntity) + + Expect(err).NotTo(HaveOccurred()) + Expect(model).To(Equal(expectedModel)) + }) + }) +}) diff --git a/pkg/transformers/bite/entity.go b/pkg/transformers/bite/entity.go new file mode 100644 index 00000000..489aba0e --- /dev/null +++ b/pkg/transformers/bite/entity.go @@ -0,0 +1,33 @@ +// 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 bite + +import ( + "github.com/ethereum/go-ethereum/core/types" + "math/big" +) + +type BiteEntity struct { + Id *big.Int + Ilk [32]byte + Lad [32]byte + Ink *big.Int + Art *big.Int + Tab *big.Int + Flip *big.Int + IArt *big.Int + TransactionIndex uint + Raw types.Log +} diff --git a/pkg/transformers/bite/integration_test.go b/pkg/transformers/bite/integration_test.go new file mode 100644 index 00000000..aa803143 --- /dev/null +++ b/pkg/transformers/bite/integration_test.go @@ -0,0 +1,83 @@ +/* + * 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 bite_test + +import ( + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" + "github.com/vulcanize/vulcanizedb/pkg/transformers/bite" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("Integration tests", func() { + XIt("Fetches bite event logs from a local test chain", func() { + ipcPath := test_config.TestClient.IPCPath + + rawRpcClient, err := rpc.Dial(ipcPath) + Expect(err).NotTo(HaveOccurred()) + + rpcClient := client.NewRpcClient(rawRpcClient, ipcPath) + ethClient := ethclient.NewClient(rawRpcClient) + blockChainClient := client.NewEthClient(ethClient) + realNode := node.MakeNode(rpcClient) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + realBlockChain := geth.NewBlockChain(blockChainClient, realNode, transactionConverter) + realFetcher := shared.NewFetcher(realBlockChain) + topic0 := common.HexToHash(bite.BiteSignature) + topics := [][]common.Hash{{topic0}} + + result, err := realFetcher.FetchLogs(test_data.TemporaryBiteAddress, topics, int64(26)) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result) > 0).To(BeTrue()) + Expect(result[0].Address).To(Equal(common.HexToAddress(test_data.TemporaryBiteAddress))) + Expect(result[0].TxHash).To(Equal(test_data.EthBiteLog.TxHash)) + Expect(result[0].BlockNumber).To(Equal(test_data.EthBiteLog.BlockNumber)) + Expect(result[0].Topics).To(Equal(test_data.EthBiteLog.Topics)) + Expect(result[0].Index).To(Equal(test_data.EthBiteLog.Index)) + }) + + It("unpacks an event log", func() { + address := common.HexToAddress(test_data.TemporaryBiteAddress) + abi, err := geth.ParseAbi(bite.BiteABI) + Expect(err).NotTo(HaveOccurred()) + + contract := bind.NewBoundContract(address, abi, nil, nil, nil) + entity := &bite.BiteEntity{} + + var eventLog = test_data.EthBiteLog + + err = contract.UnpackLog(entity, "Bite", eventLog) + Expect(err).NotTo(HaveOccurred()) + + expectedEntity := test_data.BiteEntity + Expect(entity.Art).To(Equal(expectedEntity.Art)) + Expect(entity.Ilk).To(Equal(expectedEntity.Ilk)) + Expect(entity.Ink).To(Equal(expectedEntity.Ink)) + }) +}) diff --git a/pkg/transformers/bite/model.go b/pkg/transformers/bite/model.go new file mode 100644 index 00000000..5e58f998 --- /dev/null +++ b/pkg/transformers/bite/model.go @@ -0,0 +1,28 @@ +// 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 bite + +type BiteModel struct { + Id string + Ilk []byte + Lad []byte + Ink string + Art string + IArt string + Tab string + Flip string + TransactionIndex uint `db:"tx_idx"` + Raw string `db:"raw_log"` +} diff --git a/pkg/transformers/bite/repository.go b/pkg/transformers/bite/repository.go new file mode 100644 index 00000000..bc67820f --- /dev/null +++ b/pkg/transformers/bite/repository.go @@ -0,0 +1,49 @@ +package bite + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +type Repository interface { + Create(headerID int64, model BiteModel) error + MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) +} + +type BiteRepository struct { + db *postgres.DB +} + +func NewBiteRepository(db *postgres.DB) Repository { + return BiteRepository{db: db} +} + +func (repository BiteRepository) Create(headerID int64, model BiteModel) error { + _, err := repository.db.Exec( + `INSERT into maker.bite (header_id, id, ilk, lad, ink, art, iart, tab, flip, tx_idx, raw_log) + VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, + headerID, model.Id, model.Ilk, model.Lad, model.Ink, model.Art, model.IArt, model.Tab, model.Flip, model.TransactionIndex, model.Raw, + ) + + if err != nil { + return err + } + + return nil +} +func (repository BiteRepository) MissingHeaders(startingBlockNumber int64, 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.bite 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/bite/repository_test.go b/pkg/transformers/bite/repository_test.go new file mode 100644 index 00000000..dd26d89b --- /dev/null +++ b/pkg/transformers/bite/repository_test.go @@ -0,0 +1,135 @@ +// 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 bite_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "database/sql" + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/transformers/bite" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("Bite repository", func() { + Describe("Create", func() { + It("persists a bite record", func() { + node := core.Node{} + db := test_config.NewTestDB(node) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + biteRepository := bite.NewBiteRepository(db) + + err = biteRepository.Create(headerID, test_data.BiteModel) + + Expect(err).NotTo(HaveOccurred()) + var dbBite bite.BiteModel + err = db.Get(&dbBite, `SELECT id, ilk, lad, ink, art, tab, flip, tx_idx, raw_log FROM maker.bite WHERE header_id = $1`, headerID) + Expect(err).NotTo(HaveOccurred()) + Expect(dbBite.Id).To(Equal(test_data.BiteModel.Id)) + Expect(dbBite.Ilk).To(Equal(test_data.BiteModel.Ilk)) + Expect(dbBite.Lad).To(Equal(test_data.BiteModel.Lad)) + Expect(dbBite.Art).To(Equal(test_data.BiteModel.Art)) + Expect(dbBite.Tab).To(Equal(test_data.BiteModel.Tab)) + Expect(dbBite.Flip).To(Equal(test_data.BiteModel.Flip)) + Expect(dbBite.TransactionIndex).To(Equal(test_data.BiteModel.TransactionIndex)) + Expect(dbBite.Raw).To(MatchJSON(test_data.BiteModel.Raw)) + }) + + It("does not duplicate bite events", func() { + node := core.Node{} + db := test_config.NewTestDB(node) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + biteRepository := bite.NewBiteRepository(db) + err = biteRepository.Create(headerID, test_data.BiteModel) + Expect(err).NotTo(HaveOccurred()) + + var anotherBiteModel = bite.BiteModel{ + Id: "11", + Ilk: test_data.BiteModel.Ilk, + Lad: test_data.BiteModel.Lad, + Ink: test_data.BiteModel.Ink, + Art: test_data.BiteModel.Art, + Tab: test_data.BiteModel.Tab, + Flip: test_data.BiteModel.Flip, + IArt: test_data.BiteModel.IArt, + TransactionIndex: test_data.BiteModel.TransactionIndex, + Raw: test_data.BiteModel.Raw, + } + + err = biteRepository.Create(headerID, anotherBiteModel) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("pq: duplicate key value violates unique constraint")) + }) + + It("removes bite if corresponding header is deleted", func() { + node := core.Node{} + db := test_config.NewTestDB(node) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + biteRepository := bite.NewBiteRepository(db) + err = biteRepository.Create(headerID, test_data.BiteModel) + Expect(err).NotTo(HaveOccurred()) + + _, err = db.Exec(`DELETE FROM headers WHERE id = $1`, headerID) + + Expect(err).NotTo(HaveOccurred()) + var dbBite bite.BiteModel + err = db.Get(&dbBite, `SELECT id, ilk, lad, ink, art, tab, flip, tx_idx, raw_log FROM maker.bite WHERE header_id = $1`, headerID) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(sql.ErrNoRows)) + }) + }) + + Describe("MissingHeaders", func() { + It("returns headers with no associated bite event", func() { + node := core.Node{} + db := test_config.NewTestDB(node) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + startingBlockNumber := int64(1) + biteBlockNumber := int64(2) + endingBlockNumber := int64(3) + blockNumbers := []int64{startingBlockNumber, biteBlockNumber, 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()) + } + biteRepository := bite.NewBiteRepository(db) + err := biteRepository.Create(headerIDs[1], test_data.BiteModel) + Expect(err).NotTo(HaveOccurred()) + + headers, err := biteRepository.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))) + }) + }) +}) diff --git a/pkg/transformers/bite/transformer.go b/pkg/transformers/bite/transformer.go new file mode 100644 index 00000000..3f90157d --- /dev/null +++ b/pkg/transformers/bite/transformer.go @@ -0,0 +1,88 @@ +/* + * 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 bite + +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/shared" + "log" +) + +type BiteTransformer struct { + Repository Repository + Fetcher shared.LogFetcher + Converter Converter + Config shared.TransformerConfig +} + +type BiteTransformerInitializer struct { + Config shared.TransformerConfig +} + +func (i BiteTransformerInitializer) NewBiteTransformer(db *postgres.DB, blockChain core.BlockChain) shared.Transformer { + fetcher := shared.NewFetcher(blockChain) + repository := NewBiteRepository(db) + transformer := BiteTransformer{ + Fetcher: fetcher, + Repository: repository, + Converter: BiteConverter{}, + Config: i.Config, + } + + return transformer +} + +func (b BiteTransformer) Execute() error { + config := b.Config + topics := [][]common.Hash{{common.HexToHash(shared.BiteSignature)}} + + missingHeaders, err := b.Repository.MissingHeaders(config.StartingBlockNumber, config.EndingBlockNumber) + if err != nil { + log.Println("Error fetching missing headers:", err) + return err + } + + for _, header := range missingHeaders { + ethLogs, err := b.Fetcher.FetchLogs(config.ContractAddresses, topics, header.BlockNumber) + if err != nil { + log.Println("Error fetching matching logs:", err) + return err + } + + for _, ethLog := range ethLogs { + entity, err := b.Converter.ToEntity(config.ContractAddresses, config.ContractAbi, ethLog) + model, err := b.Converter.ToModel(entity) + if err != nil { + log.Println("Error converting logs:", err) + return err + } + + err = b.Repository.Create(header.Id, model) + if err != nil { + log.Println("Error persisting bite record:", err) + return err + } + } + } + + return nil +} +func (b BiteTransformer) SetConfig(config shared.TransformerConfig) { + b.Config = config +} diff --git a/pkg/transformers/bite/transfromer_test.go b/pkg/transformers/bite/transfromer_test.go new file mode 100644 index 00000000..cdc6c8d9 --- /dev/null +++ b/pkg/transformers/bite/transfromer_test.go @@ -0,0 +1,144 @@ +/* + * 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 bite_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/bite" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks" + bite_mocks "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks/bite" + "math/rand" +) + +var _ = Describe("Bite Transformer", func() { + var repository bite_mocks.MockBiteRepository + var fetcher mocks.MockLogFetcher + var converter bite_mocks.MockBiteConverter + var transformer bite.BiteTransformer + var blockNumber1 = rand.Int63() + var blockNumber2 = rand.Int63() + var testConfig shared.TransformerConfig + + BeforeEach(func() { + repository = bite_mocks.MockBiteRepository{} + fetcher = mocks.MockLogFetcher{} + converter = bite_mocks.MockBiteConverter{} + + transformer = bite.BiteTransformer{ + Repository: &repository, + Fetcher: &fetcher, + Converter: &converter, + Config: bite.BiteConfig, + } + + testConfig = shared.TransformerConfig{ + ContractAddresses: "0x12345", + ContractAbi: "test abi", + Topics: []string{shared.BiteSignature}, + StartingBlockNumber: blockNumber1, + EndingBlockNumber: blockNumber2, + } + transformer.SetConfig(testConfig) + }) + + It("gets missing headers for blocks in the configured range", func() { + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(repository.PassedStartingBlockNumber).To(Equal(bite.BiteConfig.StartingBlockNumber)) + Expect(repository.PassedEndingBlockNumber).To(Equal(bite.BiteConfig.EndingBlockNumber)) + }) + + It("returns an error if it fails to get missing headers", func() { + repository.SetMissingHeadersErr(fakes.FakeError) + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + }) + + It("fetches eth logs for each missing header", func() { + repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1}, {BlockNumber: blockNumber2}}) + expectedTopics := [][]common.Hash{{common.HexToHash(shared.BiteSignature)}} + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(fetcher.FetchedBlocks).To(Equal([]int64{blockNumber1, blockNumber2})) + Expect(fetcher.FetchedTopics).To(Equal(expectedTopics)) + Expect(fetcher.FetchedContractAddress).To(Equal(bite.BiteConfig.ContractAddresses)) + }) + + It("returns an error if fetching logs fails", func() { + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + fetcher.SetFetcherError(fakes.FakeError) + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("converts an eth log to an Entity", func() { + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog}) + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(converter.ConverterContract).To(Equal(bite.BiteConfig.ContractAddresses)) + Expect(converter.ConverterAbi).To(Equal(bite.BiteConfig.ContractAbi)) + Expect(converter.LogsToConvert).To(Equal([]types.Log{test_data.EthBiteLog})) + }) + + It("returns an error if converter fails", func() { + headerId := int64(1) + repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1, Id: headerId}}) + fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog}) + converter.SetConverterError(fakes.FakeError) + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("persists the bite record", func() { + headerId := int64(1) + repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1, Id: headerId}}) + fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog}) + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(repository.PassedHeaderID).To(Equal(headerId)) + Expect(repository.PassedBiteModel).To(Equal(test_data.BiteModel)) + }) + + It("returns error if persisting bite record fails", func() { + repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1}}) + fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog}) + repository.SetCreateError(fakes.FakeError) + 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 54a5e1d7..5fb94b2f 100644 --- a/pkg/transformers/shared/constants.go +++ b/pkg/transformers/shared/constants.go @@ -18,4 +18,5 @@ var ( FlipperABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"era\",\"outputs\":[{\"name\":\"\",\"type\":\"uint48\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"lad\",\"type\":\"address\"},{\"name\":\"gal\",\"type\":\"address\"},{\"name\":\"tab\",\"type\":\"uint256\"},{\"name\":\"lot\",\"type\":\"uint256\"},{\"name\":\"bid\",\"type\":\"uint256\"}],\"name\":\"kick\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"vat\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"bids\",\"outputs\":[{\"name\":\"bid\",\"type\":\"uint256\"},{\"name\":\"lot\",\"type\":\"uint256\"},{\"name\":\"guy\",\"type\":\"address\"},{\"name\":\"tic\",\"type\":\"uint48\"},{\"name\":\"end\",\"type\":\"uint48\"},{\"name\":\"lad\",\"type\":\"address\"},{\"name\":\"gal\",\"type\":\"address\"},{\"name\":\"tab\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"id\",\"type\":\"uint256\"},{\"name\":\"lot\",\"type\":\"uint256\"},{\"name\":\"bid\",\"type\":\"uint256\"}],\"name\":\"tend\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"ttl\",\"outputs\":[{\"name\":\"\",\"type\":\"uint48\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"id\",\"type\":\"uint256\"},{\"name\":\"lot\",\"type\":\"uint256\"},{\"name\":\"bid\",\"type\":\"uint256\"}],\"name\":\"dent\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"beg\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"ilk\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"deal\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"tau\",\"outputs\":[{\"name\":\"\",\"type\":\"uint48\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"kicks\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"tick\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"vat_\",\"type\":\"address\"},{\"name\":\"ilk_\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"int256\"}],\"name\":\"Move\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"act\",\"type\":\"bytes32\"}],\"name\":\"Push\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"ilk\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"risk\",\"type\":\"int256\"}],\"name\":\"FileIlk\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"FileAddr\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"risk\",\"type\":\"int256\"}],\"name\":\"FileInt\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"risk\",\"type\":\"uint256\"}],\"name\":\"FileUint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"ilk\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"gem\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"dink\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"dart\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"ink\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"art\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"}],\"name\":\"Frob\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"ilk\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"gem\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"ink\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"art\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"},{\"indexed\":false,\"name\":\"tab\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"flip\",\"type\":\"uint256\"}],\"name\":\"Bite\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"ilk\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"lad\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"gem\",\"type\":\"int256\"}],\"name\":\"Slip\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"vat\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"ilk\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"lot\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"bid\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"gal\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"end\",\"type\":\"uint48\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"},{\"indexed\":false,\"name\":\"lad\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tab\",\"type\":\"uint256\"}],\"name\":\"FlipKick\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"pie\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"gem\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"lot\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"bid\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"vow\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"end\",\"type\":\"uint48\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"}],\"name\":\"FlopKick\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"pie\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"gem\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"lot\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"bid\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"gal\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"end\",\"type\":\"uint48\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"}],\"name\":\"FlapKick\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"lot\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"bid\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tic\",\"type\":\"uint48\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"}],\"name\":\"Tend\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"lot\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"bid\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tic\",\"type\":\"uint48\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"}],\"name\":\"Dent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"era\",\"type\":\"uint48\"}],\"name\":\"Deal\",\"type\":\"event\"}]" FlipKickSignature = "0x8828a22eb6a18623309ad55592866c4b077989e9e8a25e1b85f9bf6f7282520f" TendSignature = "0xd4aef477d7912041a69c5b85f2d78b618c76e40a4a92b91122c85ab5b404a64a" + BiteSignature = "0x44a8f29dfcf27e5dd7a4db50b390f851bf3ecf2bf221243be3e853f586134455" ) diff --git a/pkg/transformers/test_data/bite.go b/pkg/transformers/test_data/bite.go new file mode 100644 index 00000000..e24dcd0e --- /dev/null +++ b/pkg/transformers/test_data/bite.go @@ -0,0 +1,74 @@ +package test_data + +import ( + "encoding/json" + "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/bite" + "math/big" + "strconv" +) + +var ( + TemporaryBiteAddress = "0x4ac9588a53dc6008058c86eed71a5c91da793a07" + TemporaryBiteBlockHash = common.HexToHash("0xd130caaccc9203ca63eb149faeb013aed21f0317ce23489c0486da2f9adcd0eb") + TemporaryBiteBlockNumber = int64(26) + TemporaryBiteData = "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005" + TemporaryBiteTransaction = "0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42" +) + +var ( + biteInk = big.NewInt(1) + biteArt = big.NewInt(2) + biteTab = big.NewInt(3) + biteFlip = big.NewInt(4) + biteIArt = big.NewInt(5) + biteRawJson, _ = json.Marshal(EthBiteLog) + biteRawString = string(biteRawJson) + biteIlk = [32]byte{102, 97, 107, 101, 32, 105, 108, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + biteLad = [32]byte{102, 97, 107, 101, 32, 108, 97, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + biteId = int64(1) +) + +var EthBiteLog = types.Log{ + Address: common.HexToAddress(TemporaryBiteAddress), + Topics: []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + common.HexToHash("0x66616b6520696c6b000000000000000000000000000000000000000000000000"), + common.HexToHash("0x66616b65206c6164000000000000000000000000000000000000000000000000"), + }, + Data: hexutil.MustDecode(TemporaryBiteData), + BlockNumber: uint64(TemporaryBiteBlockNumber), + TxHash: common.HexToHash(TemporaryBiteTransaction), + TxIndex: 111, + BlockHash: TemporaryBiteBlockHash, + Index: 0, + Removed: false, +} + +var BiteEntity = bite.BiteEntity{ + Id: big.NewInt(biteId), + Ilk: biteIlk, + Lad: biteLad, + Ink: biteInk, + Art: biteArt, + Tab: biteTab, + Flip: biteFlip, + IArt: biteIArt, + TransactionIndex: EthBiteLog.TxIndex, + Raw: EthBiteLog, +} + +var BiteModel = bite.BiteModel{ + Id: strconv.FormatInt(biteId, 10), + Ilk: biteIlk[:], + Lad: biteLad[:], + Ink: biteInk.String(), + Art: biteArt.String(), + Tab: biteTab.String(), + Flip: biteFlip.String(), + IArt: biteIArt.String(), + TransactionIndex: EthBiteLog.TxIndex, + Raw: biteRawString, +} diff --git a/pkg/transformers/test_data/mocks/bite/converter.go b/pkg/transformers/test_data/mocks/bite/converter.go new file mode 100644 index 00000000..861e281d --- /dev/null +++ b/pkg/transformers/test_data/mocks/bite/converter.go @@ -0,0 +1,44 @@ +// 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 bite + +import ( + "github.com/ethereum/go-ethereum/core/types" + . "github.com/vulcanize/vulcanizedb/pkg/transformers/bite" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" +) + +type MockBiteConverter struct { + ConverterContract string + ConverterAbi string + LogsToConvert []types.Log + EntitiesToConvert []BiteEntity + ConverterError error +} + +func (mbc *MockBiteConverter) ToEntity(contractAddress string, contractAbi string, ethLog types.Log) (BiteEntity, error) { + mbc.ConverterContract = contractAddress + mbc.ConverterAbi = contractAbi + mbc.LogsToConvert = append(mbc.LogsToConvert, ethLog) + return test_data.BiteEntity, mbc.ConverterError +} + +func (mbc *MockBiteConverter) ToModel(entity BiteEntity) (BiteModel, error) { + mbc.EntitiesToConvert = append(mbc.EntitiesToConvert, entity) + return test_data.BiteModel, mbc.ConverterError +} +func (mbc *MockBiteConverter) SetConverterError(err error) { + mbc.ConverterError = err +} diff --git a/pkg/transformers/test_data/mocks/bite/repository.go b/pkg/transformers/test_data/mocks/bite/repository.go new file mode 100644 index 00000000..12a7e774 --- /dev/null +++ b/pkg/transformers/test_data/mocks/bite/repository.go @@ -0,0 +1,55 @@ +// 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 bite + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/transformers/bite" +) + +type MockBiteRepository struct { + createError error + PassedEndingBlockNumber int64 + PassedBiteModel bite.BiteModel + PassedHeaderID int64 + PassedStartingBlockNumber int64 + PassedTransactionIndex uint + missingHeaders []core.Header + missingHeadersErr error +} + +func (repository *MockBiteRepository) SetCreateError(err error) { + repository.createError = err +} + +func (repository *MockBiteRepository) SetMissingHeadersErr(err error) { + repository.missingHeadersErr = err +} + +func (repository *MockBiteRepository) SetMissingHeaders(headers []core.Header) { + repository.missingHeaders = headers +} + +func (repository *MockBiteRepository) Create(headerID int64, model bite.BiteModel) error { + repository.PassedHeaderID = headerID + repository.PassedBiteModel = model + return repository.createError +} + +func (repository *MockBiteRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) { + repository.PassedStartingBlockNumber = startingBlockNumber + repository.PassedEndingBlockNumber = endingBlockNumber + return repository.missingHeaders, repository.missingHeadersErr +} diff --git a/pkg/transformers/transformers.go b/pkg/transformers/transformers.go index ad53de4a..7278e643 100644 --- a/pkg/transformers/transformers.go +++ b/pkg/transformers/transformers.go @@ -15,6 +15,7 @@ package transformers import ( + "github.com/vulcanize/vulcanizedb/pkg/transformers/bite" "github.com/vulcanize/vulcanizedb/pkg/transformers/flip_kick" "github.com/vulcanize/vulcanizedb/pkg/transformers/frob" "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds" @@ -31,8 +32,10 @@ func TransformerInitializers() []shared.TransformerInitializer { priceFeedTransformerInitializer := price_feeds.PriceFeedTransformerInitializer{Config: priceFeedConfig} tendConfig := tend.TendConfig tendTransformerInitializer := tend.TendTransformerInitializer{Config: tendConfig} + biteTransformerInitializer := bite.BiteTransformerInitializer{Config: bite.BiteConfig} return []shared.TransformerInitializer{ + biteTransformerInitializer.NewBiteTransformer, flipKickTransformerInitializer.NewFlipKickTransformer, frobTransformerInitializer.NewFrobTransformer, priceFeedTransformerInitializer.NewPriceFeedTransformer,