diff --git a/Gododir/main.go b/Gododir/main.go index 24c8d0a7..7d3ab6f8 100644 --- a/Gododir/main.go +++ b/Gododir/main.go @@ -39,11 +39,20 @@ func tasks(p *do.Project) { p.Task("watchContract", nil, func(context *do.Context) { environment := parseEnvironment(context) contractHash := context.Args.MayString("", "contract-hash", "c") + abiFilepath := context.Args.MayString("", "abi-filepath", "a") if contractHash == "" { log.Fatalln("--contract-hash required") } - context.Start(`go run main.go --environment={{.environment}} --contract-hash={{.contractHash}}`, - do.M{"environment": environment, "contractHash": contractHash, "$in": "cmd/subscribe_contract"}) + if abiFilepath == "" { + log.Fatalln("--abi-filepath required") + } + context.Start(`go run main.go --environment={{.environment}} --contract-hash={{.contractHash}} --abi-filepath={{.abiFilepath}}`, + do.M{ + "environment": environment, + "contractHash": contractHash, + "abiFilepath": abiFilepath, + "$in": "cmd/subscribe_contract", + }) }) p.Task("migrate", nil, func(context *do.Context) { diff --git a/cmd/subscribe_contract/main.go b/cmd/subscribe_contract/main.go index 04fa4552..0028b9e9 100644 --- a/cmd/subscribe_contract/main.go +++ b/cmd/subscribe_contract/main.go @@ -4,14 +4,19 @@ import ( "flag" "github.com/8thlight/vulcanizedb/cmd" - "github.com/8thlight/vulcanizedb/pkg/core" + "github.com/8thlight/vulcanizedb/pkg/repositories" ) func main() { environment := flag.String("environment", "", "Environment name") contractHash := flag.String("contract-hash", "", "contract-hash=x1234") + abiFilepath := flag.String("abi-filepath", "", "path/to/abifile.json") flag.Parse() config := cmd.LoadConfig(*environment) repository := cmd.LoadPostgres(config.Database) - repository.CreateWatchedContract(core.WatchedContract{Hash: *contractHash}) + watchedContract := repositories.WatchedContract{ + Abi: cmd.ReadAbiFile(*abiFilepath), + Hash: *contractHash, + } + repository.CreateWatchedContract(watchedContract) } diff --git a/cmd/utils.go b/cmd/utils.go index 2ca2d1a2..3dff2da8 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -3,7 +3,10 @@ package cmd import ( "log" + "path/filepath" + "github.com/8thlight/vulcanizedb/pkg/config" + "github.com/8thlight/vulcanizedb/pkg/geth" "github.com/8thlight/vulcanizedb/pkg/repositories" ) @@ -22,3 +25,14 @@ func LoadPostgres(database config.Database) repositories.Postgres { } return repository } + +func ReadAbiFile(abiFilepath string) string { + if !filepath.IsAbs(abiFilepath) { + abiFilepath = filepath.Join(config.ProjectRoot(), abiFilepath) + } + abi, err := geth.ReadAbiFile(abiFilepath) + if err != nil { + log.Fatalf("Error reading ABI file at \"%s\"\n %v", abiFilepath, err) + } + return abi +} diff --git a/db/migrations/1512417153_add_abi_to_watched_contracts.down.sql b/db/migrations/1512417153_add_abi_to_watched_contracts.down.sql new file mode 100644 index 00000000..e22ac41f --- /dev/null +++ b/db/migrations/1512417153_add_abi_to_watched_contracts.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE watched_contracts + DROP COLUMN contract_abi; \ No newline at end of file diff --git a/db/migrations/1512417153_add_abi_to_watched_contracts.up.sql b/db/migrations/1512417153_add_abi_to_watched_contracts.up.sql new file mode 100644 index 00000000..4212ee9f --- /dev/null +++ b/db/migrations/1512417153_add_abi_to_watched_contracts.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE watched_contracts + ADD COLUMN contract_abi json; \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index 0c804371..c788d169 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 10.1 --- Dumped by pg_dump version 10.1 +-- Dumped from database version 10.0 +-- Dumped by pg_dump version 10.0 SET statement_timeout = 0; SET lock_timeout = 0; @@ -124,7 +124,8 @@ ALTER SEQUENCE transactions_id_seq OWNED BY transactions.id; CREATE TABLE watched_contracts ( contract_id integer NOT NULL, - contract_hash character varying(66) + contract_hash character varying(66), + contract_abi json ); diff --git a/pkg/core/watched_contract.go b/pkg/core/contract.go similarity index 86% rename from pkg/core/watched_contract.go rename to pkg/core/contract.go index 41bb9b1f..1686732a 100644 --- a/pkg/core/watched_contract.go +++ b/pkg/core/contract.go @@ -1,10 +1,5 @@ package core -type WatchedContract struct { - Hash string - Transactions []Transaction -} - type Contract struct { Attributes ContractAttributes Hash string @@ -20,6 +15,7 @@ type ContractAttributes []ContractAttribute func (attributes ContractAttributes) Len() int { return len(attributes) } + func (attributes ContractAttributes) Swap(i, j int) { attributes[i], attributes[j] = attributes[j], attributes[i] } diff --git a/pkg/geth/abi.go b/pkg/geth/abi.go index eba7c25f..1da6a569 100644 --- a/pkg/geth/abi.go +++ b/pkg/geth/abi.go @@ -14,14 +14,21 @@ var ( ) func ParseAbiFile(abiFilePath string) (abi.ABI, error) { - filesBytes, err := ioutil.ReadFile(abiFilePath) + abiString, err := ReadAbiFile(abiFilePath) if err != nil { return abi.ABI{}, ErrMissingAbiFile } - abiString := string(filesBytes) parsedAbi, err := abi.JSON(strings.NewReader(abiString)) if err != nil { return abi.ABI{}, ErrInvalidAbiFile } return parsedAbi, nil } + +func ReadAbiFile(abiFilePath string) (string, error) { + filesBytes, err := ioutil.ReadFile(abiFilePath) + if err != nil { + return "", ErrMissingAbiFile + } + return string(filesBytes), nil +} diff --git a/pkg/geth/abi_test.go b/pkg/geth/abi_test.go index d12ea9fc..d638a212 100644 --- a/pkg/geth/abi_test.go +++ b/pkg/geth/abi_test.go @@ -21,6 +21,15 @@ var _ = Describe("Reading ABI files", func() { Expect(err).To(BeNil()) }) + It("reads the contents of a valid ABI file", func() { + path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "valid_abi.json") + + contractAbi, err := geth.ReadAbiFile(path) + + Expect(contractAbi).To(Equal("[{\"foo\": \"bar\"}]")) + Expect(err).To(BeNil()) + }) + It("returns an error when the file does not exist", func() { path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "missing_abi.json") diff --git a/pkg/geth/contract.go b/pkg/geth/contract.go index 3c3f812c..c1b08aa8 100644 --- a/pkg/geth/contract.go +++ b/pkg/geth/contract.go @@ -51,7 +51,7 @@ func (blockchain *GethBlockchain) GetAttribute(contract core.Contract, attribute } input, err := parsed.Pack(attributeName) if err != nil { - return nil, err + return nil, ErrInvalidStateAttribute } output, err := callContract(contract, input, err, blockchain, blockNumber) if err != nil { @@ -59,7 +59,7 @@ func (blockchain *GethBlockchain) GetAttribute(contract core.Contract, attribute } err = parsed.Unpack(&result, attributeName, output) if err != nil { - return nil, ErrInvalidStateAttribute + return nil, err } return result, nil } diff --git a/pkg/geth/testing/valid_abi.json b/pkg/geth/testing/valid_abi.json index 3d80565a..5a4cf7e4 100644 --- a/pkg/geth/testing/valid_abi.json +++ b/pkg/geth/testing/valid_abi.json @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"},{"name":"_releaseTime","type":"uint256"}],"name":"mintTimelocked","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] +[{"foo": "bar"}] \ No newline at end of file diff --git a/pkg/repositories/in_memory.go b/pkg/repositories/in_memory.go index a86df78f..dfd975d7 100644 --- a/pkg/repositories/in_memory.go +++ b/pkg/repositories/in_memory.go @@ -6,10 +6,10 @@ import ( type InMemory struct { blocks map[int64]*core.Block - watchedContracts map[string]*core.WatchedContract + watchedContracts map[string]*WatchedContract } -func (repository *InMemory) CreateWatchedContract(watchedContract core.WatchedContract) error { +func (repository *InMemory) CreateWatchedContract(watchedContract WatchedContract) error { repository.watchedContracts[watchedContract.Hash] = &watchedContract return nil } @@ -19,19 +19,19 @@ func (repository *InMemory) IsWatchedContract(contractHash string) bool { return present } -func (repository *InMemory) FindWatchedContract(contractHash string) *core.WatchedContract { - var transactions []core.Transaction - if _, ok := repository.watchedContracts[contractHash]; !ok { +func (repository *InMemory) FindWatchedContract(contractHash string) *WatchedContract { + watchedContract, ok := repository.watchedContracts[contractHash] + if !ok { return nil } for _, block := range repository.blocks { for _, transaction := range block.Transactions { if transaction.To == contractHash { - transactions = append(transactions, transaction) + watchedContract.Transactions = append(watchedContract.Transactions, transaction) } } } - return &core.WatchedContract{Hash: contractHash, Transactions: transactions} + return watchedContract } func (repository *InMemory) MissingBlockNumbers(startingBlockNumber int64, endingBlockNumber int64) []int64 { @@ -47,7 +47,7 @@ func (repository *InMemory) MissingBlockNumbers(startingBlockNumber int64, endin func NewInMemory() *InMemory { return &InMemory{ blocks: make(map[int64]*core.Block), - watchedContracts: make(map[string]*core.WatchedContract), + watchedContracts: make(map[string]*WatchedContract), } } diff --git a/pkg/repositories/postgres.go b/pkg/repositories/postgres.go index 7d0d4d36..e36b7fa1 100644 --- a/pkg/repositories/postgres.go +++ b/pkg/repositories/postgres.go @@ -31,9 +31,14 @@ func NewPostgres(databaseConfig config.Database) (Postgres, error) { return Postgres{Db: db}, nil } -func (repository Postgres) CreateWatchedContract(contract core.WatchedContract) error { +func (repository Postgres) CreateWatchedContract(contract WatchedContract) error { + abi := contract.Abi + var abiToInsert *string + if abi != "" { + abiToInsert = &abi + } _, err := repository.Db.Exec( - `INSERT INTO watched_contracts (contract_hash) VALUES ($1)`, contract.Hash) + `INSERT INTO watched_contracts (contract_hash, contract_abi) VALUES ($1, $2)`, contract.Hash, abiToInsert) if err != nil { return ErrDBInsertFailed } @@ -47,10 +52,10 @@ func (repository Postgres) IsWatchedContract(contractHash string) bool { return exists } -func (repository Postgres) FindWatchedContract(contractHash string) *core.WatchedContract { - var savedContracts []core.WatchedContract +func (repository Postgres) FindWatchedContract(contractHash string) *WatchedContract { + var savedContracts []WatchedContract contractRows, _ := repository.Db.Query( - `SELECT contract_hash FROM watched_contracts WHERE contract_hash=$1`, contractHash) + `SELECT contract_hash, contract_abi FROM watched_contracts WHERE contract_hash=$1`, contractHash) savedContracts = repository.loadContract(contractRows) if len(savedContracts) > 0 { return &savedContracts[0] @@ -192,14 +197,15 @@ func (repository Postgres) loadTransactions(transactionRows *sql.Rows) []core.Tr return transactions } -func (repository Postgres) loadContract(contractRows *sql.Rows) []core.WatchedContract { - var savedContracts []core.WatchedContract +func (repository Postgres) loadContract(contractRows *sql.Rows) []WatchedContract { + var savedContracts []WatchedContract for contractRows.Next() { var savedContractHash string - contractRows.Scan(&savedContractHash) + var savedContractAbi string + contractRows.Scan(&savedContractHash, &savedContractAbi) transactionRows, _ := repository.Db.Query(`SELECT tx_hash, tx_nonce, tx_to, tx_from, tx_gaslimit, tx_gasprice, tx_value FROM transactions WHERE tx_to = $1 ORDER BY block_id desc`, savedContractHash) transactions := repository.loadTransactions(transactionRows) - savedContract := core.WatchedContract{Hash: savedContractHash, Transactions: transactions} + savedContract := WatchedContract{Hash: savedContractHash, Transactions: transactions, Abi: savedContractAbi} savedContracts = append(savedContracts, savedContract) } return savedContracts diff --git a/pkg/repositories/repository.go b/pkg/repositories/repository.go index bc246b2b..93581f3b 100644 --- a/pkg/repositories/repository.go +++ b/pkg/repositories/repository.go @@ -8,7 +8,7 @@ type Repository interface { FindBlockByNumber(blockNumber int64) *core.Block MaxBlockNumber() int64 MissingBlockNumbers(startingBlockNumber int64, endingBlockNumber int64) []int64 - CreateWatchedContract(contract core.WatchedContract) error + CreateWatchedContract(contract WatchedContract) error IsWatchedContract(contractHash string) bool - FindWatchedContract(contractHash string) *core.WatchedContract + FindWatchedContract(contractHash string) *WatchedContract } diff --git a/pkg/repositories/testing/helpers.go b/pkg/repositories/testing/helpers.go index 199f6374..766a37be 100644 --- a/pkg/repositories/testing/helpers.go +++ b/pkg/repositories/testing/helpers.go @@ -210,7 +210,7 @@ func AssertRepositoryBehavior(buildRepository func() repositories.Repository) { Describe("Creating watched contracts", func() { It("returns the watched contract when it exists", func() { - repository.CreateWatchedContract(core.WatchedContract{Hash: "x123"}) + repository.CreateWatchedContract(repositories.WatchedContract{Hash: "x123"}) watchedContract := repository.FindWatchedContract("x123") Expect(watchedContract).NotTo(BeNil()) @@ -226,11 +226,10 @@ func AssertRepositoryBehavior(buildRepository func() repositories.Repository) { }) It("returns empty array when no transactions 'To' a watched contract", func() { - repository.CreateWatchedContract(core.WatchedContract{Hash: "x123"}) + repository.CreateWatchedContract(repositories.WatchedContract{Hash: "x123"}) watchedContract := repository.FindWatchedContract("x123") Expect(watchedContract).ToNot(BeNil()) Expect(watchedContract.Transactions).To(BeEmpty()) - }) It("returns transactions 'To' a watched contract", func() { @@ -244,7 +243,7 @@ func AssertRepositoryBehavior(buildRepository func() repositories.Repository) { } repository.CreateBlock(block) - repository.CreateWatchedContract(core.WatchedContract{Hash: "x123"}) + repository.CreateWatchedContract(repositories.WatchedContract{Hash: "x123"}) watchedContract := repository.FindWatchedContract("x123") Expect(watchedContract).ToNot(BeNil()) Expect(watchedContract.Transactions).To( @@ -253,6 +252,16 @@ func AssertRepositoryBehavior(buildRepository func() repositories.Repository) { {Hash: "TRANSACTION3", To: "x123"}, })) }) + + It("stores the ABI of the contract", func() { + repository.CreateWatchedContract(repositories.WatchedContract{ + Abi: "{\"some\": \"json\"}", + Hash: "x123", + }) + watchedContract := repository.FindWatchedContract("x123") + Expect(watchedContract).ToNot(BeNil()) + Expect(watchedContract.Abi).To(Equal("{\"some\": \"json\"}")) + }) }) } diff --git a/pkg/repositories/watched_contract.go b/pkg/repositories/watched_contract.go new file mode 100644 index 00000000..14daaa62 --- /dev/null +++ b/pkg/repositories/watched_contract.go @@ -0,0 +1,9 @@ +package repositories + +import "github.com/8thlight/vulcanizedb/pkg/core" + +type WatchedContract struct { + Abi string + Hash string + Transactions []core.Transaction +} diff --git a/pkg/watched_contracts/summary.go b/pkg/watched_contracts/summary.go index 71bf112d..beae1b08 100644 --- a/pkg/watched_contracts/summary.go +++ b/pkg/watched_contracts/summary.go @@ -39,7 +39,7 @@ func (contractSummary ContractSummary) GetStateAttribute(attributeName string) i return result } -func newContractSummary(blockchain core.Blockchain, watchedContract core.WatchedContract, blockNumber *big.Int) ContractSummary { +func newContractSummary(blockchain core.Blockchain, watchedContract repositories.WatchedContract, blockNumber *big.Int) ContractSummary { contract, _ := blockchain.GetContract(watchedContract.Hash) return ContractSummary{ blockChain: blockchain, @@ -52,9 +52,9 @@ func newContractSummary(blockchain core.Blockchain, watchedContract core.Watched } } -func lastTransaction(contract core.WatchedContract) *core.Transaction { - if len(contract.Transactions) > 0 { - return &contract.Transactions[0] +func lastTransaction(watchedContract repositories.WatchedContract) *core.Transaction { + if len(watchedContract.Transactions) > 0 { + return &watchedContract.Transactions[0] } else { return nil } diff --git a/pkg/watched_contracts/summary_test.go b/pkg/watched_contracts/summary_test.go index 8308ad03..af8d4542 100644 --- a/pkg/watched_contracts/summary_test.go +++ b/pkg/watched_contracts/summary_test.go @@ -32,7 +32,7 @@ var _ = Describe("The watched contract summary", func() { Context("when the given contract is being watched", func() { It("returns the summary", func() { repository := repositories.NewInMemory() - watchedContract := core.WatchedContract{Hash: "0x123"} + watchedContract := repositories.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) blockchain := fakes.NewBlockchain() @@ -44,7 +44,7 @@ var _ = Describe("The watched contract summary", func() { It("includes the contract hash in the summary", func() { repository := repositories.NewInMemory() - watchedContract := core.WatchedContract{Hash: "0x123"} + watchedContract := repositories.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) blockchain := fakes.NewBlockchain() @@ -55,7 +55,7 @@ var _ = Describe("The watched contract summary", func() { It("sets the number of transactions", func() { repository := repositories.NewInMemory() - watchedContract := core.WatchedContract{Hash: "0x123"} + watchedContract := repositories.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) block := core.Block{ Transactions: []core.Transaction{ @@ -73,7 +73,7 @@ var _ = Describe("The watched contract summary", func() { It("sets the last transaction", func() { repository := repositories.NewInMemory() - watchedContract := core.WatchedContract{Hash: "0x123"} + watchedContract := repositories.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) block := core.Block{ Transactions: []core.Transaction{ @@ -91,7 +91,7 @@ var _ = Describe("The watched contract summary", func() { It("gets contract state attribute for the contract from the blockchain", func() { repository := repositories.NewInMemory() - watchedContract := core.WatchedContract{Hash: "0x123"} + watchedContract := repositories.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) blockchain := fakes.NewBlockchain() blockchain.SetContractStateAttribute("0x123", nil, "foo", "bar") @@ -104,7 +104,7 @@ var _ = Describe("The watched contract summary", func() { It("gets contract state attribute for the contract from the blockchain at specific block height", func() { repository := repositories.NewInMemory() - watchedContract := core.WatchedContract{Hash: "0x123"} + watchedContract := repositories.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) blockchain := fakes.NewBlockchain() blockNumber := big.NewInt(1000) @@ -119,7 +119,7 @@ var _ = Describe("The watched contract summary", func() { It("gets attributes for the contract from the blockchain", func() { repository := repositories.NewInMemory() - watchedContract := core.WatchedContract{Hash: "0x123"} + watchedContract := repositories.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) blockchain := fakes.NewBlockchain() blockchain.SetContractStateAttribute("0x123", nil, "foo", "bar")