Repository: onflow/flow-cli (go)

Issue:
flow project deploy with wrong account
### Problem

flow.json has an account under deployments/emulator that is not present in the flow.json file for that network

flow project deploy -n emulator
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x4a24b5a]

goroutine 1 [running]:
github.com/onflow/flow-cli/pkg/flowcli/project.(*Project).ContractsByNetwork(0xc00014c3f0, 0x7ffeefbff9e7, 0x8, 0xc000387af0, 0x40105e8, 0x4fb7f80)
	github.com/onflow/flow-cli/pkg/flowcli/project/project.go:187 +0x31a
github.com/onflow/flow-cli/pkg/flowcli/project.(*Project).ContractConflictExists(0xc00014c3f0, 0x7ffeefbff9e7, 0x8, 0x15)
	github.com/onflow/flow-cli/pkg/flowcli/project/project.go:133 +0x45
github.com/onflow/flow-cli/pkg/flowcli/services.(*Project).Deploy(0xc00014cc30, 0x7ffeefbff9e7, 0x8, 0xc000617c00, 0xc00014cba0, 0xc00014cbd0, 0xc00014ccc0, 0xc00014cc00, 0xc00014cb10)
	github.com/onflow/flow-cli/pkg/flowcli/services/project.go:104 +0x6c
github.com/onflow/flow-cli/internal/project.glob..func1(0x5c3d7c0, 0xc000138fe0, 0x0, 0x2, 0x0, 0x0, 0x50042c5, 0x4, 0x0, 0x0, ...)
	github.com/onflow/flow-cli/internal/project/deploy.go:47 +0x5b
github.com/onflow/flow-cli/internal/command.Command.AddToParent.func1(0x5c3d7c0, 0xc000138fe0, 0x0, 0x2)
	github.com/onflow/flow-cli/internal/command/command.go:170 +0x2dd
github.com/spf13/cobra.(*Command).execute(0x5c3d7c0, 0xc00003c080, 0x2, 0x2, 0x5c3d7c0, 0xc00003c080)
	github.com/spf13/cobra@v0.0.7/command.go:842 +0x2c2
github.com/spf13/cobra.(*Command).ExecuteC(0xc0001c7340, 0xc000387f70, 0x1, 0x1)
	github.com/spf13/cobra@v0.0.7/command.go:943 +0x336
github.com/spf13/cobra.(*Command).Execute(...)
	github.com/spf13/cobra@v0.0.7/command.go:883
main.main()
	github.com/onflow/flow-cli/cmd/flow/main.go:65 +0x365


Pull request description:
QA Check account and contract exists
Closes #194 

## Description
Explicitly check that account and contracts exists on deployments. If the user inputted the wrong account name in the configuration it could crash CLI with null pointer exception.

______

For contributor use:

- [ ] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels


Patch:
diff --git a/pkg/flowcli/config/config.go b/pkg/flowcli/config/config.go
index 07ed99a9..9b92e442 100644
--- a/pkg/flowcli/config/config.go
+++ b/pkg/flowcli/config/config.go
@@ -163,7 +163,7 @@ func (c *Contract) IsAlias() bool {
 }
 
 // GetByNameAndNetwork get contract array for account and network
-func (c *Contracts) GetByNameAndNetwork(name string, network string) Contract {
+func (c *Contracts) GetByNameAndNetwork(name string, network string) *Contract {
 	contracts := make(Contracts, 0)
 
 	for _, contract := range *c {
@@ -176,15 +176,18 @@ func (c *Contracts) GetByNameAndNetwork(name string, network string) Contract {
 	// and replace only name and source with existing
 	if len(contracts) == 0 {
 		cName := c.GetByName(name)
+		if cName == nil {
+			return nil
+		}
 
-		return Contract{
+		return &Contract{
 			Name:    cName.Name,
 			Network: network,
 			Source:  cName.Source,
 		}
 	}
 
-	return contracts[0]
+	return &contracts[0]
 }
 
 // TODO: this filtering can cause error if not found, better to refactor to returning
diff --git a/pkg/flowcli/project/project.go b/pkg/flowcli/project/project.go
index f741a443..582f6751 100644
--- a/pkg/flowcli/project/project.go
+++ b/pkg/flowcli/project/project.go
@@ -131,7 +131,10 @@ func newProject(conf *config.Config, composer *config.Loader) (*Project, error)
 // The CLI currently does not allow the same contract to be deployed to multiple
 // accounts in the same network.
 func (p *Project) ContractConflictExists(network string) bool {
-	contracts := p.ContractsByNetwork(network)
+	contracts, err := p.ContractsByNetwork(network)
+	if err != nil {
+		return false
+	}
 
 	uniq := funk.Uniq(
 		funk.Map(contracts, func(c Contract) string {
@@ -171,16 +174,22 @@ func (p *Project) SetEmulatorServiceKey(privateKey crypto.PrivateKey) {
 }
 
 // ContractsByNetwork returns all contracts for a network.
-func (p *Project) ContractsByNetwork(network string) []Contract {
+func (p *Project) ContractsByNetwork(network string) ([]Contract, error) {
 	contracts := make([]Contract, 0)
 
 	// get deployments for the specified network
 	for _, deploy := range p.conf.Deployments.GetByNetwork(network) {
 		account := p.AccountByName(deploy.Account)
+		if account == nil {
+			return nil, fmt.Errorf("could not find account with name %s in the configuration", deploy.Account)
+		}
 
 		// go through each contract in this deployment
 		for _, contractName := range deploy.Contracts {
 			c := p.conf.Contracts.GetByNameAndNetwork(contractName, network)
+			if c == nil {
+				return nil, fmt.Errorf("could not find contract with name name %s in the configuration", contractName)
+			}
 
 			contract := Contract{
 				Name:   c.Name,
@@ -192,7 +201,7 @@ func (p *Project) ContractsByNetwork(network string) []Contract {
 		}
 	}
 
-	return contracts
+	return contracts, nil
 }
 
 // AccountNamesForNetwork returns all configured account names for a network.
diff --git a/pkg/flowcli/services/project.go b/pkg/flowcli/services/project.go
index 632f95a1..5fb656d9 100644
--- a/pkg/flowcli/services/project.go
+++ b/pkg/flowcli/services/project.go
@@ -109,7 +109,12 @@ func (p *Project) Deploy(network string, update bool) ([]*contracts.Contract, er
 		p.project.AliasesForNetwork(network),
 	)
 
-	for _, contract := range p.project.ContractsByNetwork(network) {
+	contractsNetwork, err := p.project.ContractsByNetwork(network)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, contract := range contractsNetwork {
 		err := processor.AddContractSource(
 			contract.Name,
 			contract.Source,
@@ -120,7 +125,7 @@ func (p *Project) Deploy(network string, update bool) ([]*contracts.Contract, er
 		}
 	}
 
-	err := processor.ResolveImports()
+	err = processor.ResolveImports()
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/flowcli/services/scripts.go b/pkg/flowcli/services/scripts.go
index b2dd9721..9aa72d5c 100644
--- a/pkg/flowcli/services/scripts.go
+++ b/pkg/flowcli/services/scripts.go
@@ -88,9 +88,14 @@ func (s *Scripts) execute(code []byte, args []string, argsJSON string, scriptPat
 			return nil, fmt.Errorf("resolving imports in scripts not supported")
 		}
 
+		contractsNetwork, err := s.project.ContractsByNetwork(network)
+		if err != nil {
+			return nil, err
+		}
+
 		code, err = resolver.ResolveImports(
 			scriptPath,
-			s.project.ContractsByNetwork(network),
+			contractsNetwork,
 			s.project.AliasesForNetwork(network),
 		)
 		if err != nil {
diff --git a/pkg/flowcli/services/transactions.go b/pkg/flowcli/services/transactions.go
index 2cc7f1aa..4803c9cf 100644
--- a/pkg/flowcli/services/transactions.go
+++ b/pkg/flowcli/services/transactions.go
@@ -145,9 +145,14 @@ func (t *Transactions) Build(
 			return nil, fmt.Errorf("resolving imports in transactions not supported")
 		}
 
+		contractsNetwork, err := t.project.ContractsByNetwork(network)
+		if err != nil {
+			return nil, err
+		}
+
 		code, err = resolver.ResolveImports(
 			codeFilename,
-			t.project.ContractsByNetwork(network),
+			contractsNetwork,
 			t.project.AliasesForNetwork(network),
 		)
 		if err != nil {
@@ -263,7 +268,7 @@ func (t *Transactions) Send(
 		return nil, nil, err
 	}
 
-	t.logger.StartProgress(fmt.Sprintf("Sending Transaction with ID: %s", signed.FlowTransaction().ID()))
+	t.logger.StartProgress("Sending Transaction...")
 
 	sentTx, err := t.gateway.SendSignedTransaction(signed)
 	if err != nil {


Test patch:
diff --git a/pkg/flowcli/project/project_test.go b/pkg/flowcli/project/project_test.go
index a89af259..5b6f401c 100644
--- a/pkg/flowcli/project/project_test.go
+++ b/pkg/flowcli/project/project_test.go
@@ -318,7 +318,7 @@ Project Tests
 func Test_GetContractsByNameSimple(t *testing.T) {
 	p := generateSimpleProject()
 
-	contracts := p.ContractsByNetwork("emulator")
+	contracts, _ := p.ContractsByNetwork("emulator")
 
 	assert.Len(t, contracts, 1)
 	assert.Equal(t, contracts[0].Name, "NonFungibleToken")
@@ -360,7 +360,7 @@ func Test_HostSimple(t *testing.T) {
 func Test_GetContractsByNameComplex(t *testing.T) {
 	p := generateComplexProject()
 
-	contracts := p.ContractsByNetwork("emulator")
+	contracts, _ := p.ContractsByNetwork("emulator")
 
 	assert.Equal(t, 7, len(contracts))
 
@@ -452,7 +452,7 @@ func Test_GetAliases(t *testing.T) {
 	p := generateAliasesProject()
 
 	aliases := p.AliasesForNetwork("emulator")
-	contracts := p.ContractsByNetwork("emulator")
+	contracts, _ := p.ContractsByNetwork("emulator")
 
 	assert.Len(t, aliases, 1)
 	assert.Equal(t, aliases["../hungry-kitties/cadence/contracts/FungibleToken.cdc"], "ee82856bf20e2aa6")
@@ -464,10 +464,10 @@ func Test_GetAliasesComplex(t *testing.T) {
 	p := generateAliasesComplexProject()
 
 	aEmulator := p.AliasesForNetwork("emulator")
-	cEmulator := p.ContractsByNetwork("emulator")
+	cEmulator, _ := p.ContractsByNetwork("emulator")
 
 	aTestnet := p.AliasesForNetwork("testnet")
-	cTestnet := p.ContractsByNetwork("testnet")
+	cTestnet, _ := p.ContractsByNetwork("testnet")
 
 	assert.Len(t, cEmulator, 1)
 	assert.Equal(t, cEmulator[0].Name, "NonFungibleToken")