diff --git a/.gitignore b/.gitignore index 7af61243..dc7c69c8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ Gododir/godobin-* test_data_dir/ vendor/ +contracts/* environments/*.toml diff --git a/Gopkg.lock b/Gopkg.lock index cb46a706..ed3ad2cc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -13,6 +13,12 @@ packages = ["."] revision = "4748e29d5718c2df4028a6543edf86fd8cc0f881" +[[projects]] + branch = "master" + name = "github.com/aristanetworks/goarista" + packages = ["monotime"] + revision = "54fadd0c513d502544edf098480238dc9da50f9e" + [[projects]] branch = "master" name = "github.com/btcsuite/btcd" @@ -21,7 +27,7 @@ [[projects]] name = "github.com/ethereum/go-ethereum" - packages = [".","common","common/hexutil","common/math","core/types","crypto","crypto/secp256k1","crypto/sha3","ethclient","log","params","rlp","rpc","trie"] + packages = [".","accounts","accounts/abi","accounts/abi/bind","accounts/keystore","common","common/hexutil","common/math","common/mclock","core/types","crypto","crypto/randentropy","crypto/secp256k1","crypto/sha3","ethclient","event","log","params","rlp","rpc","trie"] revision = "1db4ecdc0b9e828ff65777fb466fc7c1d04e0de9" version = "v1.7.2" @@ -103,12 +109,24 @@ revision = "c893efa28eb45626cdaa76c9f653b62488858837" version = "v1.2.0" +[[projects]] + name = "github.com/pborman/uuid" + packages = ["."] + revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53" + version = "v1.1" + [[projects]] branch = "master" name = "github.com/rcrowley/go-metrics" packages = ["."] revision = "1f30fe9094a513ce4c700b9a54458bbb0c96996c" +[[projects]] + branch = "master" + name = "github.com/rjeczalik/notify" + packages = ["."] + revision = "767eb674ef14b09119b2fff3601e64558d530c47" + [[projects]] name = "github.com/rs/cors" packages = ["."] @@ -118,7 +136,7 @@ [[projects]] branch = "master" name = "golang.org/x/crypto" - packages = ["ssh/terminal"] + packages = ["pbkdf2","scrypt","ssh/terminal"] revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8" [[projects]] @@ -139,6 +157,12 @@ packages = ["encoding","encoding/charmap","encoding/htmlindex","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","encoding/unicode","internal/gen","internal/tag","internal/utf8internal","language","runes","transform","unicode/cldr"] revision = "c01e4764d870b77f8abe5096ee19ad20d80e8075" +[[projects]] + branch = "master" + name = "golang.org/x/tools" + packages = ["go/ast/astutil","imports"] + revision = "6d70fb2e85323e81c89374331d3d2b93304faa36" + [[projects]] name = "gopkg.in/fatih/set.v0" packages = ["."] @@ -172,6 +196,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f9569275c3b9863e5209029135f13d3d9df9a697b49e75b5cbd37942b23e4f3b" + inputs-digest = "d2aa2bdc1442319cde6fe38b39e0ff8e25b1a0b0c120a7e2fab8065324c98693" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/run/main.go b/cmd/run/main.go index e04a3d6e..7b9c094c 100644 --- a/cmd/run/main.go +++ b/cmd/run/main.go @@ -17,9 +17,9 @@ func main() { environment := flag.String("environment", "", "Environment name") flag.Parse() config := cmd.LoadConfig(*environment) - fmt.Println("Client Path ", config.Client.IPCPath) repository := repositories.NewPostgres(config.Database) + fmt.Printf("Creating Geth Blockchain to: %s\n", config.Client.IPCPath) listener := blockchain_listener.NewBlockchainListener( geth.NewGethBlockchain(config.Client.IPCPath), []core.BlockchainObserver{ diff --git a/cmd/show_contract_summary/main.go b/cmd/show_contract_summary/main.go index 712897ee..98d6d55b 100644 --- a/cmd/show_contract_summary/main.go +++ b/cmd/show_contract_summary/main.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/8thlight/vulcanizedb/cmd" + "github.com/8thlight/vulcanizedb/pkg/geth" "github.com/8thlight/vulcanizedb/pkg/repositories" "github.com/8thlight/vulcanizedb/pkg/watched_contracts" ) @@ -18,8 +19,9 @@ func main() { flag.Parse() config := cmd.LoadConfig(*environment) + blockchain := geth.NewGethBlockchain(config.Client.IPCPath) repository := repositories.NewPostgres(config.Database) - contractSummary, err := watched_contracts.NewSummary(repository, *contractHash) + contractSummary, err := watched_contracts.NewSummary(blockchain, repository, *contractHash) if err != nil { log.Fatalln(err) } diff --git a/db/schema.sql b/db/schema.sql index e6434288..0c804371 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -2,7 +2,7 @@ -- PostgreSQL database dump -- --- Dumped from database version 10.0 +-- Dumped from database version 10.1 -- Dumped by pg_dump version 10.1 SET statement_timeout = 0; diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 3c140426..0036036d 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -5,4 +5,6 @@ type Blockchain interface { SubscribeToBlocks(blocks chan Block) StartListening() StopListening() + GetContractAttributes(contractHash string) (ContractAttributes, error) + GetContractStateAttribute(contractHash string, attributeName string) (*string, error) } diff --git a/pkg/core/watched_contract.go b/pkg/core/watched_contract.go index 8547d20d..85df30af 100644 --- a/pkg/core/watched_contract.go +++ b/pkg/core/watched_contract.go @@ -4,3 +4,21 @@ type WatchedContract struct { Hash string Transactions []Transaction } + +type ContractAttribute struct { + Name string + Type string +} + +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] +} + +func (attributes ContractAttributes) Less(i, j int) bool { + return attributes[i].Name < attributes[j].Name +} diff --git a/pkg/fakes/blockchain.go b/pkg/fakes/blockchain.go index b7b7c4f8..966f5112 100644 --- a/pkg/fakes/blockchain.go +++ b/pkg/fakes/blockchain.go @@ -1,15 +1,40 @@ package fakes -import "github.com/8thlight/vulcanizedb/pkg/core" +import ( + "github.com/8thlight/vulcanizedb/pkg/core" + "sort" +) type Blockchain struct { - blocks map[int64]core.Block - blocksChannel chan core.Block - WasToldToStop bool + blocks map[int64]core.Block + contractAttributes map[string]map[string]string + blocksChannel chan core.Block + WasToldToStop bool +} + +func (blockchain *Blockchain) GetContractAttributes(contractHash string) (core.ContractAttributes, error) { + var contractAttributes core.ContractAttributes + attributes, ok := blockchain.contractAttributes[contractHash] + if ok { + for key, _ := range attributes { + contractAttributes = append(contractAttributes, core.ContractAttribute{Name: key, Type: "string"}) + } + } + sort.Sort(contractAttributes) + return contractAttributes, nil +} + +func (blockchain *Blockchain) GetContractStateAttribute(contractHash string, attributeName string) (*string, error) { + result := new(string) + *result = blockchain.contractAttributes[contractHash][attributeName] + return result, nil } func NewBlockchain() *Blockchain { - return &Blockchain{blocks: make(map[int64]core.Block)} + return &Blockchain{ + blocks: make(map[int64]core.Block), + contractAttributes: make(map[string]map[string]string), + } } func NewBlockchainWithBlocks(blocks []core.Block) *Blockchain { @@ -40,3 +65,11 @@ func (*Blockchain) StartListening() {} func (blockchain *Blockchain) StopListening() { blockchain.WasToldToStop = true } + +func (blockchain *Blockchain) SetContractStateAttribute(contractHash string, attributeName string, attributeValue string) { + contractStateAttributes := blockchain.contractAttributes[contractHash] + if contractStateAttributes == nil { + blockchain.contractAttributes[contractHash] = make(map[string]string) + } + blockchain.contractAttributes[contractHash][attributeName] = attributeValue +} diff --git a/pkg/geth/abi.go b/pkg/geth/abi.go new file mode 100644 index 00000000..eba7c25f --- /dev/null +++ b/pkg/geth/abi.go @@ -0,0 +1,27 @@ +package geth + +import ( + "errors" + "io/ioutil" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var ( + ErrInvalidAbiFile = errors.New("invalid abi") + ErrMissingAbiFile = errors.New("missing abi") +) + +func ParseAbiFile(abiFilePath string) (abi.ABI, error) { + filesBytes, err := ioutil.ReadFile(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 +} diff --git a/pkg/geth/abi_test.go b/pkg/geth/abi_test.go new file mode 100644 index 00000000..d12ea9fc --- /dev/null +++ b/pkg/geth/abi_test.go @@ -0,0 +1,42 @@ +package geth_test + +import ( + "path/filepath" + + cfg "github.com/8thlight/vulcanizedb/pkg/config" + "github.com/8thlight/vulcanizedb/pkg/geth" + "github.com/ethereum/go-ethereum/accounts/abi" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Reading ABI files", func() { + + It("loads a valid ABI file", func() { + path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "valid_abi.json") + + contractAbi, err := geth.ParseAbiFile(path) + + Expect(contractAbi).NotTo(BeNil()) + 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") + + contractAbi, err := geth.ParseAbiFile(path) + + Expect(contractAbi).To(Equal(abi.ABI{})) + Expect(err).To(Equal(geth.ErrMissingAbiFile)) + }) + + It("returns an error when the file has invalid contents", func() { + path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "invalid_abi.json") + + contractAbi, err := geth.ParseAbiFile(path) + + Expect(contractAbi).To(Equal(abi.ABI{})) + Expect(err).To(Equal(geth.ErrInvalidAbiFile)) + }) + +}) diff --git a/pkg/geth/contract.go b/pkg/geth/contract.go new file mode 100644 index 00000000..de65de3e --- /dev/null +++ b/pkg/geth/contract.go @@ -0,0 +1,54 @@ +package geth + +import ( + "errors" + "fmt" + "path/filepath" + + "sort" + + "github.com/8thlight/vulcanizedb/pkg/config" + "github.com/8thlight/vulcanizedb/pkg/core" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +var ( + ErrInvalidStateAttribute = errors.New("invalid state attribute") +) + +func (blockchain *GethBlockchain) GetContractAttributes(contractHash string) (core.ContractAttributes, error) { + abiFilePath := filepath.Join(config.ProjectRoot(), "contracts", "public", fmt.Sprintf("%s.json", contractHash)) + parsed, _ := ParseAbiFile(abiFilePath) + var contractAttributes core.ContractAttributes + for _, abiElement := range parsed.Methods { + if (len(abiElement.Outputs) > 0) && (len(abiElement.Inputs) == 0) && abiElement.Const && abiElement.Outputs[0].Type.String() == "string" { + attributeType := abiElement.Outputs[0].Type.String() + contractAttributes = append(contractAttributes, core.ContractAttribute{abiElement.Name, attributeType}) + } + } + sort.Sort(contractAttributes) + return contractAttributes, nil +} + +func (blockchain *GethBlockchain) GetContractStateAttribute(contractHash string, attributeName string) (*string, error) { + boundContract, err := bindContract(common.HexToAddress(contractHash), blockchain.client, blockchain.client) + if err != nil { + return nil, err + } + result := new(string) + err = boundContract.Call(&bind.CallOpts{}, result, attributeName) + if err != nil { + return nil, ErrInvalidStateAttribute + } + return result, nil +} + +func bindContract(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + abiFilePath := filepath.Join(config.ProjectRoot(), "contracts", "public", fmt.Sprintf("%s.json", address.Hex())) + parsed, err := ParseAbiFile(abiFilePath) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} diff --git a/pkg/geth/contract_test.go b/pkg/geth/contract_test.go new file mode 100644 index 00000000..acd2792f --- /dev/null +++ b/pkg/geth/contract_test.go @@ -0,0 +1,100 @@ +package geth_test + +//import ( +// cfg "github.com/8thlight/vulcanizedb/pkg/config" +// "github.com/8thlight/vulcanizedb/pkg/geth" +// "github.com/8thlight/vulcanizedb/pkg/geth/testing" +// . "github.com/onsi/ginkgo" +// . "github.com/onsi/gomega" +//) +// +//var _ = Describe("Reading contracts", func() { +// +// Describe("Reading the list of attributes", func() { +// It("returns a string attribute for a real contract", func() { +// config, _ := cfg.NewConfig("public") +// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) +// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" +// +// attributes, err := blockchain.GetContractAttributes(contractHash) +// +// Expect(err).To(BeNil()) +// Expect(len(attributes)).NotTo(Equal(0)) +// symbolAttribute := *testing.FindAttribute(attributes, "symbol") +// Expect(symbolAttribute.Name).To(Equal("symbol")) +// Expect(symbolAttribute.Type).To(Equal("string")) +// }) +// +// It("does not return an attribute that takes an input", func() { +// config, _ := cfg.NewConfig("public") +// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) +// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" +// +// attributes, err := blockchain.GetContractAttributes(contractHash) +// +// Expect(err).To(BeNil()) +// attribute := testing.FindAttribute(attributes, "balanceOf") +// Expect(attribute).To(BeNil()) +// }) +// +// It("does not return an attribute that is not constant", func() { +// config, _ := cfg.NewConfig("public") +// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) +// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" +// +// attributes, err := blockchain.GetContractAttributes(contractHash) +// +// Expect(err).To(BeNil()) +// attribute := testing.FindAttribute(attributes, "unpause") +// Expect(attribute).To(BeNil()) +// }) +// +// It("temporarily filters out non-string attributes", func() { +// config, _ := cfg.NewConfig("public") +// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) +// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" +// +// attributes, err := blockchain.GetContractAttributes(contractHash) +// +// Expect(err).To(BeNil()) +// attribute := testing.FindAttribute(attributes, "decimals") +// Expect(attribute).To(BeNil()) +// }) +// }) +// +// Describe("Getting a contract attribute", func() { +// It("returns the correct attribute for a real contract", func() { +// config, _ := cfg.NewConfig("public") +// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) +// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" +// +// name, err := blockchain.GetContractStateAttribute(contractHash, "name") +// +// Expect(*name).To(Equal("OMGToken")) +// Expect(err).To(BeNil()) +// }) +// +// It("returns an error when there is no ABI for the given contract", func() { +// config, _ := cfg.NewConfig("public") +// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) +// contractHash := "MISSINGHASH" +// +// name, err := blockchain.GetContractStateAttribute(contractHash, "name") +// +// Expect(name).To(BeNil()) +// Expect(err).To(Equal(geth.ErrMissingAbiFile)) +// }) +// +// It("returns an error when asking for an attribute that does not exist", func() { +// config, _ := cfg.NewConfig("public") +// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) +// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" +// +// name, err := blockchain.GetContractStateAttribute(contractHash, "missing_attribute") +// +// Expect(err).To(Equal(geth.ErrInvalidStateAttribute)) +// Expect(name).To(BeNil()) +// }) +// }) +// +//}) diff --git a/pkg/geth/geth_blockchain.go b/pkg/geth/geth_blockchain.go index 60ea34f8..d2ba8012 100644 --- a/pkg/geth/geth_blockchain.go +++ b/pkg/geth/geth_blockchain.go @@ -25,7 +25,6 @@ func (blockchain *GethBlockchain) GetBlockByNumber(blockNumber int64) core.Block } func NewGethBlockchain(ipcPath string) *GethBlockchain { - fmt.Printf("Creating Geth Blockchain to: %s\n", ipcPath) blockchain := GethBlockchain{} client, _ := ethclient.Dial(ipcPath) blockchain.client = client diff --git a/pkg/geth/testing/contract_attributes.go b/pkg/geth/testing/contract_attributes.go new file mode 100644 index 00000000..a6cd7de3 --- /dev/null +++ b/pkg/geth/testing/contract_attributes.go @@ -0,0 +1,14 @@ +package testing + +import ( + "github.com/8thlight/vulcanizedb/pkg/core" +) + +func FindAttribute(contractAttributes core.ContractAttributes, attributeName string) *core.ContractAttribute { + for _, contractAttribute := range contractAttributes { + if contractAttribute.Name == attributeName { + return &contractAttribute + } + } + return nil +} diff --git a/pkg/geth/testing/invalid_abi.json b/pkg/geth/testing/invalid_abi.json new file mode 100644 index 00000000..2a260b84 --- /dev/null +++ b/pkg/geth/testing/invalid_abi.json @@ -0,0 +1 @@ +bad json \ No newline at end of file diff --git a/pkg/geth/testing/valid_abi.json b/pkg/geth/testing/valid_abi.json new file mode 100644 index 00000000..3d80565a --- /dev/null +++ b/pkg/geth/testing/valid_abi.json @@ -0,0 +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"}] diff --git a/pkg/watched_contracts/console_presenter.go b/pkg/watched_contracts/console_presenter.go index d63aedc8..6563d080 100644 --- a/pkg/watched_contracts/console_presenter.go +++ b/pkg/watched_contracts/console_presenter.go @@ -11,15 +11,18 @@ func GenerateConsoleOutput(summary *ContractSummary) string { summary.ContractHash, summary.NumberOfTransactions, transactionToString(summary.LastTransaction), + attributesString(summary), ) } func template() string { return `********************Contract Summary*********************** - HASH: %v - NUMBER OF TRANSACTIONS: %d - LAST TRANSACTION: - %s + HASH: %v + NUMBER OF TRANSACTIONS: %d + LAST TRANSACTION: + %s + ATTRIBUTES: + %s ` } @@ -28,7 +31,20 @@ func transactionToString(transaction *core.Transaction) string { return "NONE" } else { return fmt.Sprintf(`Hash: %s - To: %s - From: %s`, transaction.Hash, transaction.To, transaction.From) + To: %s + From: %s`, transaction.Hash, transaction.To, transaction.From) } } + +func attributesString(summary *ContractSummary) string { + var formattedAttributes string + for _, attribute := range summary.Attributes { + formattedAttributes += formatAttribute(attribute.Name, summary) + "\n" + " " + } + return formattedAttributes +} + +func formatAttribute(attributeName string, summary *ContractSummary) string { + formattedAttribute := fmt.Sprintf("%s: %s", attributeName, summary.GetStateAttribute(attributeName)) + return formattedAttribute +} diff --git a/pkg/watched_contracts/summary.go b/pkg/watched_contracts/summary.go index a97e6344..3e9183a6 100644 --- a/pkg/watched_contracts/summary.go +++ b/pkg/watched_contracts/summary.go @@ -12,26 +12,36 @@ type ContractSummary struct { ContractHash string NumberOfTransactions int LastTransaction *core.Transaction + blockChain core.Blockchain + Attributes core.ContractAttributes } var NewContractNotWatchedErr = func(contractHash string) error { return errors.New(fmt.Sprintf("Contract %v not being watched", contractHash)) } -func NewSummary(repository repositories.Repository, contractHash string) (*ContractSummary, error) { - contract := repository.FindWatchedContract(contractHash) - if contract != nil { - return newContractSummary(*contract), nil +func NewSummary(blockchain core.Blockchain, repository repositories.Repository, contractHash string) (*ContractSummary, error) { + watchedContract := repository.FindWatchedContract(contractHash) + if watchedContract != nil { + return newContractSummary(blockchain, *watchedContract), nil } else { return nil, NewContractNotWatchedErr(contractHash) } } -func newContractSummary(contract core.WatchedContract) *ContractSummary { +func (contractSummary ContractSummary) GetStateAttribute(attributeName string) string { + result, _ := contractSummary.blockChain.GetContractStateAttribute(contractSummary.ContractHash, attributeName) + return *result +} + +func newContractSummary(blockchain core.Blockchain, watchedContract core.WatchedContract) *ContractSummary { + attributes, _ := blockchain.GetContractAttributes(watchedContract.Hash) return &ContractSummary{ - ContractHash: contract.Hash, - NumberOfTransactions: len(contract.Transactions), - LastTransaction: lastTransaction(contract), + blockChain: blockchain, + ContractHash: watchedContract.Hash, + NumberOfTransactions: len(watchedContract.Transactions), + LastTransaction: lastTransaction(watchedContract), + Attributes: attributes, } } diff --git a/pkg/watched_contracts/summary_test.go b/pkg/watched_contracts/summary_test.go index 2f2344ab..6c4886e5 100644 --- a/pkg/watched_contracts/summary_test.go +++ b/pkg/watched_contracts/summary_test.go @@ -2,6 +2,7 @@ package watched_contracts_test import ( "github.com/8thlight/vulcanizedb/pkg/core" + "github.com/8thlight/vulcanizedb/pkg/fakes" "github.com/8thlight/vulcanizedb/pkg/repositories" "github.com/8thlight/vulcanizedb/pkg/watched_contracts" . "github.com/onsi/ginkgo" @@ -13,8 +14,9 @@ var _ bool = Describe("The watched contract summary", func() { Context("when the given contract is not being watched", func() { It("returns an error", func() { repository := repositories.NewInMemory() + blockchain := fakes.NewBlockchain() - contractSummary, err := watched_contracts.NewSummary(repository, "123") + contractSummary, err := watched_contracts.NewSummary(blockchain, repository, "123") Expect(contractSummary).To(BeNil()) Expect(err).NotTo(BeNil()) @@ -26,8 +28,9 @@ var _ bool = Describe("The watched contract summary", func() { repository := repositories.NewInMemory() watchedContract := core.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) + blockchain := fakes.NewBlockchain() - contractSummary, err := watched_contracts.NewSummary(repository, "0x123") + contractSummary, err := watched_contracts.NewSummary(blockchain, repository, "0x123") Expect(contractSummary).NotTo(BeNil()) Expect(err).To(BeNil()) @@ -37,8 +40,9 @@ var _ bool = Describe("The watched contract summary", func() { repository := repositories.NewInMemory() watchedContract := core.WatchedContract{Hash: "0x123"} repository.CreateWatchedContract(watchedContract) + blockchain := fakes.NewBlockchain() - contractSummary, _ := watched_contracts.NewSummary(repository, "0x123") + contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") Expect(contractSummary.ContractHash).To(Equal("0x123")) }) @@ -54,8 +58,9 @@ var _ bool = Describe("The watched contract summary", func() { }, } repository.CreateBlock(block) + blockchain := fakes.NewBlockchain() - contractSummary, _ := watched_contracts.NewSummary(repository, "0x123") + contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") Expect(contractSummary.NumberOfTransactions).To(Equal(2)) }) @@ -71,11 +76,42 @@ var _ bool = Describe("The watched contract summary", func() { }, } repository.CreateBlock(block) + blockchain := fakes.NewBlockchain() - contractSummary, _ := watched_contracts.NewSummary(repository, "0x123") + contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") Expect(contractSummary.LastTransaction.Hash).To(Equal("TRANSACTION2")) }) + + It("gets contract state attribute for the contract from the blockchain", func() { + repository := repositories.NewInMemory() + watchedContract := core.WatchedContract{Hash: "0x123"} + repository.CreateWatchedContract(watchedContract) + blockchain := fakes.NewBlockchain() + blockchain.SetContractStateAttribute("0x123", "foo", "bar") + + contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") + + Expect(contractSummary.GetStateAttribute("foo")).To(Equal("bar")) + }) + + It("gets attributes for the contract from the blockchain", func() { + repository := repositories.NewInMemory() + watchedContract := core.WatchedContract{Hash: "0x123"} + repository.CreateWatchedContract(watchedContract) + blockchain := fakes.NewBlockchain() + blockchain.SetContractStateAttribute("0x123", "foo", "bar") + blockchain.SetContractStateAttribute("0x123", "baz", "bar") + + contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") + + Expect(contractSummary.Attributes).To(Equal( + core.ContractAttributes{ + {Name: "baz", Type: "string"}, + {Name: "foo", Type: "string"}, + }, + )) + }) }) })