Repository: onflow/flow-cli (go)

Issue:
`access(account)` bug in contract update validator
Account access is not being resolved properly in the staged contract update validator.  Even if the contract is being deployed to an account with access, the checker reports that it is missing `account` access for the staged contract.

Pull request:
Fix `access(account)` bug in contract update validator
Closes #1524

## Description

Fixes bug related to `access(account)` not resolving properly due to use of string location for the staged contract checker.

______

For contributor use:

- [ ] Targeted PR against `master` branch
- [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [x] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [x] Updated relevant documentation
- [x] Re-reviewed `Files changed` in the Github PR explorer
- [x] Added appropriate labels


Patch:
diff --git a/go.mod b/go.mod
index a7b2d8e9..eb0a6947 100644
--- a/go.mod
+++ b/go.mod
@@ -18,7 +18,7 @@ require (
 	github.com/onflow/flixkit-go v1.2.1-cadence-v1-preview.10
 	github.com/onflow/flow-core-contracts/lib/go/templates v0.15.2-0.20240402184019-90048578066e
 	github.com/onflow/flow-emulator v1.0.0-preview.18
-	github.com/onflow/flow-evm-gateway v0.5.1-0.20240412143816-f27b2f135b79
+	github.com/onflow/flow-evm-gateway v0.5.1-0.20240414162736-e85fa156688b
 	github.com/onflow/flow-go v0.34.0-crescendo-preview.10.0.20240412141002-8081bde9d8a7
 	github.com/onflow/flow-go-sdk v1.0.0-preview.18
 	github.com/onflow/flowkit/v2 v2.0.0-stable-cadence-alpha.14
diff --git a/go.sum b/go.sum
index 7eced5f4..880a167f 100644
--- a/go.sum
+++ b/go.sum
@@ -2098,8 +2098,8 @@ github.com/onflow/flow-core-contracts/lib/go/templates v0.15.2-0.20240402184019-
 github.com/onflow/flow-core-contracts/lib/go/templates v0.15.2-0.20240402184019-90048578066e/go.mod h1:CA6ON8bUZWkZnlhYNsvs3Pox1G6dFtBp/wPZSQA7ivQ=
 github.com/onflow/flow-emulator v1.0.0-preview.18 h1:ZrlUzPTF1gYjMsg8k6x5DvgnCjcHJrvvio9K8ImlNXY=
 github.com/onflow/flow-emulator v1.0.0-preview.18/go.mod h1:GSJ8GCxNMdjBEpeg77KM19mFxIQL9dg98KJgLG0/2DI=
-github.com/onflow/flow-evm-gateway v0.5.1-0.20240412143816-f27b2f135b79 h1:bNHudrSCSejR7LBWkmiujNv02SbmuzkhxPVy59Xqo78=
-github.com/onflow/flow-evm-gateway v0.5.1-0.20240412143816-f27b2f135b79/go.mod h1:AGZhLWHmsQSWCk96FC6xQ6gjcxlIN2Bng3RRrtkRa7M=
+github.com/onflow/flow-evm-gateway v0.5.1-0.20240414162736-e85fa156688b h1:YDw6ikc3sW7Jzezf59VXuoKEKCSi5GuX+R2MelPNM10=
+github.com/onflow/flow-evm-gateway v0.5.1-0.20240414162736-e85fa156688b/go.mod h1:AGZhLWHmsQSWCk96FC6xQ6gjcxlIN2Bng3RRrtkRa7M=
 github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20240402160548-a9c331660956 h1:WbG97gmdbgfYZT8YCyye0fAwz4k5vditXcPGoy63m9M=
 github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20240402160548-a9c331660956/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A=
 github.com/onflow/flow-ft/lib/go/templates v0.7.1-0.20240402160548-a9c331660956 h1:Ef9UKtwNcHVG2R8YskYiwRoaTZFhAVmQ0ZN3c0eDUGU=
diff --git a/internal/evm/gateway.go b/internal/evm/gateway.go
index 02d6dffb..126dfd88 100644
--- a/internal/evm/gateway.go
+++ b/internal/evm/gateway.go
@@ -56,7 +56,7 @@ type gatewayFlag struct {
 	RPCPort            int    `flag:"rpc-port" default:"3000" info:"port for the RPC API server"`
 	AccessNodeGRPCHost string `flag:"access-node-grpc-host" default:"localhost:3569" info:"host to the flow access node gRPC API"`
 	InitCadenceHeight  uint64 `flag:"init-cadence-height" default:"0" info:"init cadence block height from where the event ingestion will start. WARNING: you should only provide this if there are no existing values in the database, otherwise an error will be thrown"`
-	EVMNetworkID       string `flag:"evm-network-id" default:"testnet" info:"EVM network ID (testnet, mainnet)"`
+	EVMNetworkID       string `flag:"evm-network-id" default:"previewnet" info:"EVM network ID (testnet, mainnet, previewnet)"`
 	FlowNetworkID      string `flag:"flow-network-id" default:"emulator" info:"EVM network ID (emulator, previewnet)"`
 	Coinbase           string `flag:"coinbase" default:"" info:"coinbase address to use for fee collection"`
 	GasPrice           string `flag:"gas-price" default:"1" info:"static gas price used for EVM transactions"`
@@ -123,6 +123,8 @@ var gatewayCommand = &command.Command{
 			cfg.EVMNetworkID = types.FlowEVMTestNetChainID
 		case "mainnet":
 			cfg.EVMNetworkID = types.FlowEVMMainNetChainID
+		case "previewnet":
+			cfg.EVMNetworkID = types.FlowEVMPreviewNetChainID
 		default:
 			return nil, fmt.Errorf("EVM network ID not supported")
 		}
diff --git a/internal/migrate/staging_validator.go b/internal/migrate/staging_validator.go
index 11decd16..ba4d1c9e 100644
--- a/internal/migrate/staging_validator.go
+++ b/internal/migrate/staging_validator.go
@@ -46,6 +46,11 @@ type stagingValidator struct {
 	flow  flowkit.Services
 	state *flowkit.State
 
+	// Location of the source code that is used for the update
+	sourceCodeLocation common.Location
+	// Location of the target contract that is being updated
+	targetLocation common.AddressLocation
+
 	// Cache for account contract names so we don't have to fetch them multiple times
 	accountContractNames map[common.Address][]string
 	// All resolved contract code
@@ -99,6 +104,9 @@ func (v *stagingValidator) ValidateContractUpdate(
 	// Code of the updated contract
 	updatedCode []byte,
 ) error {
+	v.sourceCodeLocation = sourceCodeLocation
+	v.targetLocation = location
+
 	// Resolve all system contract code & add to cache
 	v.loadSystemContracts()
 
@@ -192,8 +200,9 @@ func (v *stagingValidator) parseAndCheckContract(
 				// Only checking contracts, so no need to consider script standard library
 				return util.NewStandardLibrary().BaseValueActivation
 			},
-			LocationHandler: v.resolveLocation,
-			ImportHandler:   v.resolveImport,
+			LocationHandler:            v.resolveLocation,
+			ImportHandler:              v.resolveImport,
+			MemberAccountAccessHandler: v.resolveAccountAccess,
 		},
 	)
 	if err != nil {
@@ -381,6 +390,26 @@ func (v *stagingValidator) resolveLocation(
 	return resolvedLocations, nil
 }
 
+func (v *stagingValidator) resolveAccountAccess(checker *sema.Checker, memberLocation common.Location) bool {
+	if checker == nil {
+		return false
+	}
+
+	checkerLocation, ok := checker.Location.(common.StringLocation)
+	if !ok {
+		return false
+	}
+
+	memberAddressLocation, ok := memberLocation.(common.AddressLocation)
+	if !ok {
+		return false
+	}
+
+	// If the source code of the update is being checked, we should check account access based on the
+	// targeted network location of the contract & not the source code location
+	return checkerLocation == v.sourceCodeLocation && memberAddressLocation.Address == v.targetLocation.Address
+}
+
 func (v *stagingValidator) resolveAddressContractNames(address common.Address) ([]string, error) {
 	// Check if the contract names are already cached
 	if names, ok := v.accountContractNames[address]; ok {


Test patch:
diff --git a/internal/migrate/staging_validator_test.go b/internal/migrate/staging_validator_test.go
index 48db6e70..fd2e5070 100644
--- a/internal/migrate/staging_validator_test.go
+++ b/internal/migrate/staging_validator_test.go
@@ -288,4 +288,65 @@ func Test_StagingValidator(t *testing.T) {
 		err := validator.ValidateContractUpdate(location, sourceCodeLocation, []byte(newContract))
 		require.NoError(t, err)
 	})
+
+	t.Run("resolves account access correctly", func(t *testing.T) {
+		location := common.NewAddressLocation(nil, common.Address{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "Test")
+		sourceCodeLocation := common.StringLocation("./Test.cdc")
+		oldContract := `
+		import ImpContract from 0x01
+		pub contract Test {
+			pub fun test() {}
+		}`
+		newContract := `
+		import ImpContract from 0x01
+		access(all) contract Test {
+			access(all) fun test() {}
+			init() {
+				ImpContract.test()
+			}
+		}`
+		impContract := `
+		access(all) contract ImpContract {
+			access(account) fun test() {}
+			init() {}
+		}`
+		mockScriptResultString, err := cadence.NewString(impContract)
+		require.NoError(t, err)
+
+		mockAccount := &flow.Account{
+			Address: flow.HexToAddress("01"),
+			Balance: 1000,
+			Keys:    nil,
+			Contracts: map[string][]byte{
+				"Test": []byte(oldContract),
+			},
+		}
+
+		// setup mocks
+		require.NoError(t, rw.WriteFile(sourceCodeLocation.String(), []byte(newContract), 0o644))
+		srv.GetAccount.Run(func(args mock.Arguments) {
+			require.Equal(t, flow.HexToAddress("01"), args.Get(1).(flow.Address))
+		}).Return(mockAccount, nil)
+		srv.Network.Return(config.Network{
+			Name: "testnet",
+		}, nil)
+		srv.ExecuteScript.Run(func(args mock.Arguments) {
+			script := args.Get(1).(flowkit.Script)
+
+			assert.Equal(t, templates.GenerateGetStagedContractCodeScript(MigrationContractStagingAddress("testnet")), script.Code)
+
+			assert.Equal(t, 2, len(script.Args))
+			actualContractAddressArg, actualContractNameArg := script.Args[0], script.Args[1]
+
+			contractName, _ := cadence.NewString("ImpContract")
+			contractAddr := cadence.NewAddress(flow.HexToAddress("01"))
+			assert.Equal(t, contractName, actualContractNameArg)
+			assert.Equal(t, contractAddr, actualContractAddressArg)
+		}).Return(cadence.NewOptional(mockScriptResultString), nil)
+
+		// validate
+		validator := newStagingValidator(srv.Mock, state)
+		err = validator.ValidateContractUpdate(location, sourceCodeLocation, []byte(newContract))
+		require.NoError(t, err)
+	})
 }