Skip to content

Commit

Permalink
Replace automatic core contract resolution from flow flix generate
Browse files Browse the repository at this point in the history
…with dependency manager prompts (#1853)
  • Loading branch information
jribbink authored Dec 6, 2024
1 parent 77c1364 commit 8234adb
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 44 deletions.
15 changes: 2 additions & 13 deletions internal/dependencymanager/dependencyinstaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,17 +392,6 @@ func (di *DependencyInstaller) handleFileSystem(contractAddr, contractName, cont
return nil
}

func isCoreContract(contractName string) bool {
sc := systemcontracts.SystemContractsForChain(flowGo.Emulator)

for _, coreContract := range sc.All() {
if coreContract.Name == contractName {
return true
}
}
return false
}

func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, assignedName, contractName string, program *project.Program) error {
hash := sha256.New()
hash.Write(program.CodeWithUnprocessedImports())
Expand Down Expand Up @@ -455,7 +444,7 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as

func (di *DependencyInstaller) handleAdditionalDependencyTasks(networkName, contractName string) error {
// If the contract is not a core contract and the user does not want to skip deployments, then prompt for a deployment
if !di.SkipDeployments && !isCoreContract(contractName) {
if !di.SkipDeployments && !util.IsCoreContract(contractName) {
err := di.updateDependencyDeployment(contractName)
if err != nil {
di.Logger.Error(fmt.Sprintf("Error updating deployment: %v", err))
Expand All @@ -467,7 +456,7 @@ func (di *DependencyInstaller) handleAdditionalDependencyTasks(networkName, cont
}

// If the contract is not a core contract and the user does not want to skip aliasing, then prompt for an alias
if !di.SkipAlias && !isCoreContract(contractName) {
if !di.SkipAlias && !util.IsCoreContract(contractName) {
err := di.updateDependencyAlias(contractName, networkName)
if err != nil {
di.Logger.Error(fmt.Sprintf("Error updating alias: %v", err))
Expand Down
61 changes: 30 additions & 31 deletions internal/super/flix.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ import (
"fmt"
"os"

"github.com/onflow/cadence/runtime/parser"
"github.com/onflow/flixkit-go/v2/flixkit"
"github.com/onflow/flow-go/fvm/systemcontracts"
"github.com/onflow/flow-go/model/flow"

"github.com/spf13/cobra"

Expand All @@ -38,6 +37,7 @@ import (
"github.com/onflow/flow-cli/internal/command"
"github.com/onflow/flow-cli/internal/scripts"
"github.com/onflow/flow-cli/internal/transactions"
"github.com/onflow/flow-cli/internal/util"
)

type flixFlags struct {
Expand Down Expand Up @@ -225,7 +225,6 @@ func generateFlixCmd(
) (result command.Result, err error) {
cadenceFile := args[0]
depContracts := getContractsFromState(state, flags.ExcludeNetworks)
coreContracts, err := getCoreContracts(flags.ExcludeNetworks)
if err != nil {
return nil, fmt.Errorf("could not get core contracts %w", err)
}
Expand Down Expand Up @@ -261,15 +260,12 @@ func generateFlixCmd(
}
}

ctx := context.Background()

// merge deployed contracts and core contracts
for name, addresses := range coreContracts {
if _, exists := depContracts[name]; !exists {
depContracts[name] = addresses
}
err = validateImports(string(code), depContracts, depNetworks)
if err != nil {
return nil, fmt.Errorf("could not validate imported contracts: %w", err)
}

ctx := context.Background()
prettyJSON, err := flixService.CreateTemplate(ctx, depContracts, string(code), flags.PreFill, depNetworks)
if err != nil {
return nil, fmt.Errorf("could not generate flix %w", err)
Expand All @@ -296,34 +292,37 @@ func (fr *flixResult) Oneliner() string {
return fr.result
}

func getCoreContracts(excludeNetworks []string) (flixkit.ContractInfos, error) {
coreContracts := make(flixkit.ContractInfos)

networkConfigs := []struct {
chainID flow.ChainID
networkName string
excluded bool
}{
{flow.ChainID("flow-mainnet"), config.MainnetNetwork.Name, slices.Contains(excludeNetworks, config.MainnetNetwork.Name)},
{flow.ChainID("flow-testnet"), config.TestnetNetwork.Name, slices.Contains(excludeNetworks, config.TestnetNetwork.Name)},
{flow.ChainID("flow-emulator"), config.EmulatorNetwork.Name, slices.Contains(excludeNetworks, config.EmulatorNetwork.Name)},
func validateImports(code string, depContracts flixkit.ContractInfos, depNetworks []flixkit.NetworkConfig) error {
// Check all imported contracts in the cadence code
astProgram, err := parser.ParseProgram(nil, []byte(code), parser.Config{})
if err != nil {
return fmt.Errorf("could not parse Cadence code %w", err)
}

for _, nc := range networkConfigs {
if nc.excluded {
continue
// Check for any missing string imports
for _, imp := range astProgram.ImportDeclarations() {
if len(imp.Identifiers) > 0 || imp.Location == nil {
return fmt.Errorf("only string imports of the form `import \"ContractName\"` are supported")
}

contracts := systemcontracts.SystemContractsForChain(nc.chainID)
for _, c := range contracts.All() {
if _, exists := coreContracts[c.Name]; !exists {
coreContracts[c.Name] = make(flixkit.NetworkAddressMap)
contractName := imp.Location.String()

if depContracts[contractName] == nil {
if util.IsCoreContract(contractName) {
return fmt.Errorf("contract %[1]s is not found in the flow.json configuration, if this refers to the %[1]s core contract, please add it using `flow deps install %[1]s`", contractName)
}

return fmt.Errorf("contract %[1]s is not found in the flow.json configuration, if it refers to an external contract, please add it using `flow deps install <network>://<address>.%[1]s`", contractName)
}

for _, network := range depNetworks {
if depContracts[contractName][network.Name] == "" {
return fmt.Errorf("contract %s was found in the flow.json configuration, but is missing an alias for network %s", contractName, network.Name)
}
coreContracts[c.Name][nc.networkName] = c.Address.HexWithPrefix()
}
}

return coreContracts, nil
return nil
}

func getContractsFromState(state *flowkit.State, excludeNetworks []string) flixkit.ContractInfos {
Expand Down Expand Up @@ -385,7 +384,7 @@ func getContractsFromState(state *flowkit.State, excludeNetworks []string) flixk
}

// add contracts that have not been deployed
for _, c := range *state.Dependencies() {
for _, c := range *state.Contracts() {
if _, ok := allContracts[c.Name]; !ok {
allContracts[c.Name] = make(flixkit.NetworkAddressMap)
}
Expand Down
92 changes: 92 additions & 0 deletions internal/super/flix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/stretchr/testify/mock"

"github.com/onflow/flixkit-go/v2/flixkit"
"github.com/onflow/flow-go-sdk"

"github.com/onflow/flowkit/v2"
"github.com/onflow/flowkit/v2/config"
Expand Down Expand Up @@ -278,3 +279,94 @@ func Test_GenerateFlixPrefill(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, result)
}

func Test_GenerateFlixMissingCoreContract(t *testing.T) {
logger := output.NewStdoutLogger(output.NoneLog)
srv := mocks.DefaultMockServices()
cadenceFile := "cadence.cdc"

mockFS := afero.NewMemMapFs()
rw := afero.Afero{Fs: mockFS}
script := "import \"FungibleToken\"\n access(all) fun main() {}"
err := rw.WriteFile(cadenceFile, []byte(script), 0o644)
assert.NoError(t, err)
state, _ := flowkit.Init(rw)

mockFlixService := new(MockFlixService)

_, err = generateFlixCmd(
[]string{cadenceFile},
command.GlobalFlags{},
logger,
srv.Mock,
state,
mockFlixService,
flixFlags{
ExcludeNetworks: []string{"emulator", "testnet"},
},
)
assert.Error(t, err)
assert.Contains(t, err.Error(), "flow deps install FungibleToken")
}

func Test_GenerateFlixMissingExternalContract(t *testing.T) {
logger := output.NewStdoutLogger(output.NoneLog)
srv := mocks.DefaultMockServices()
cadenceFile := "cadence.cdc"

mockFS := afero.NewMemMapFs()
rw := afero.Afero{Fs: mockFS}
script := "import \"SomeContract\"\n access(all) fun main() {}"
err := rw.WriteFile(cadenceFile, []byte(script), 0o644)
assert.NoError(t, err)
state, _ := flowkit.Init(rw)

mockFlixService := new(MockFlixService)

_, err = generateFlixCmd(
[]string{cadenceFile},
command.GlobalFlags{},
logger,
srv.Mock,
state,
mockFlixService,
flixFlags{
ExcludeNetworks: []string{"emulator", "testnet"},
},
)
assert.Error(t, err)
assert.Contains(t, err.Error(), "flow deps install <network>://<address>.SomeContract")
}

func Test_GenerateFlixMissingAlias(t *testing.T) {
logger := output.NewStdoutLogger(output.NoneLog)
srv := mocks.DefaultMockServices()
cadenceFile := "cadence.cdc"

mockFS := afero.NewMemMapFs()
rw := afero.Afero{Fs: mockFS}
script := "import \"Foobar\"\n access(all) fun main() {}"
err := rw.WriteFile(cadenceFile, []byte(script), 0o644)
assert.NoError(t, err)
state, _ := flowkit.Init(rw)
state.Contracts().AddOrUpdate(config.Contract{
Name: "Foobar",
Aliases: []config.Alias{{Address: flow.Address{0x01}, Network: "mainnet"}},
})

mockFlixService := new(MockFlixService)

_, err = generateFlixCmd(
[]string{cadenceFile},
command.GlobalFlags{},
logger,
srv.Mock,
state,
mockFlixService,
flixFlags{
ExcludeNetworks: []string{"emulator"},
},
)
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing an alias")
}
13 changes: 13 additions & 0 deletions internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"github.com/onflow/flow-go-sdk"
flowsdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go/fvm/systemcontracts"
flowGo "github.com/onflow/flow-go/model/flow"

"github.com/onflow/flowkit/v2"
"github.com/onflow/flowkit/v2/accounts"
Expand Down Expand Up @@ -175,3 +177,14 @@ func Pluralize(word string, count int) string {
}
return word + "s"
}

func IsCoreContract(contractName string) bool {
sc := systemcontracts.SystemContractsForChain(flowGo.Emulator)

for _, coreContract := range sc.All() {
if coreContract.Name == contractName {
return true
}
}
return false
}

0 comments on commit 8234adb

Please sign in to comment.