From 8b1c89e529271557ab9c51739dfde8647f623d2f Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Fri, 6 Dec 2024 15:08:21 -0600 Subject: [PATCH] Update Flixkit & Add Error Message for Missing Dependencies (#1852) * update to use latest version of flixkit-go, pass in core contracts as dependencies * fix lint issue * add example in usage, add abstraction for getting core contracts * add user's dependencies from flow.json as deps for generating flix * Replace automatic core contract resolution from `flow flix generate` with dependency manager prompts (#1853) * fix tests * fix test * format --------- Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Co-authored-by: Jordan Ribbink --- go.mod | 12 +- go.sum | 24 +-- .../dependencymanager/dependencyinstaller.go | 15 +- internal/super/flix.go | 106 +++++++++-- internal/super/flix_test.go | 180 ++++++++++++++++-- internal/util/util.go | 24 ++- 6 files changed, 284 insertions(+), 77 deletions(-) diff --git a/go.mod b/go.mod index d887d9f8b..dafb2c0dc 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/onflow/cadence-tools/lint v1.0.0 github.com/onflow/cadence-tools/test v1.0.0 github.com/onflow/fcl-dev-wallet v0.8.0 - github.com/onflow/flixkit-go/v2 v2.0.1 + github.com/onflow/flixkit-go/v2 v2.2.1 github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0 github.com/onflow/flow-emulator v1.0.1 github.com/onflow/flow-evm-gateway v0.36.5-0.20241005010031-1f3f5439d553 @@ -203,7 +203,7 @@ require ( github.com/multiformats/go-multistream v0.5.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onflow/atree v0.8.0-rc.6 // indirect + github.com/onflow/atree v0.8.0 // indirect github.com/onflow/crypto v0.25.2 // indirect github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 // indirect github.com/onflow/flow-ft/lib/go/contracts v1.0.0 // indirect @@ -273,13 +273,13 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gonum.org/v1/gonum v0.14.0 // indirect diff --git a/go.sum b/go.sum index 0175a0f6d..b94de1570 100644 --- a/go.sum +++ b/go.sum @@ -2154,8 +2154,8 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= -github.com/onflow/atree v0.8.0-rc.6 h1:GWgaylK24b5ta2Hq+TvyOF7X5tZLiLzMMn7lEt59fsA= -github.com/onflow/atree v0.8.0-rc.6/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= +github.com/onflow/atree v0.8.0 h1:qg5c6J1gVDNObughpEeWm8oxqhPGdEyGrda121GM4u0= +github.com/onflow/atree v0.8.0/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= github.com/onflow/cadence v1.0.0 h1:bvT75F2LZJvDCBmmajAv7QLISK6Qp30FAKcSwqNNH+o= github.com/onflow/cadence v1.0.0/go.mod h1:7wvvecnAZtYOspLOS3Lh+FuAmMeSrXhAWiycC3kQ1UU= @@ -2170,8 +2170,8 @@ github.com/onflow/crypto v0.25.2 h1:GjHunqVt+vPcdqhxxhAXiMIF3YiLX7gTuTR5O+VG2ns= github.com/onflow/crypto v0.25.2/go.mod h1:fY7eLqUdMKV8EGOw301unP8h7PvLVy8/6gVR++/g0BY= github.com/onflow/fcl-dev-wallet v0.8.0 h1:8TWHhJBWrzS6RCZI3eVjRT+SaUBqO6eygUNDaJV/B7s= github.com/onflow/fcl-dev-wallet v0.8.0/go.mod h1:kc42jkiuoPJmxMRFjfbRO9XvnR/3XLheaOerxVMDTiw= -github.com/onflow/flixkit-go/v2 v2.0.1 h1:hbGiP5mdi9HPlsGSSnPtsTmQiaZXDkcpV3Cfn4+ffUA= -github.com/onflow/flixkit-go/v2 v2.0.1/go.mod h1:TVF6tdM5AMVhRtq1LcYqjOJDoz54TUyrMZvZUHNrX+o= +github.com/onflow/flixkit-go/v2 v2.2.1 h1:DXE55Ckrfgfr6V+Bn5ci0RSb/+Fi3n65N8Ogv91D24Y= +github.com/onflow/flixkit-go/v2 v2.2.1/go.mod h1:iSyTybGXubZ6gv0NRzveSeI5PvOmShMqS3WYVT7tLAg= github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 h1:q9tXLIALwQ76bO4bmSrhtTkyc2cZF4/gH11ix9E3F5k= github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1/go.mod h1:u/mkP/B+PbV33tEG3qfkhhBlydSvAKxfLZSfB4lsJHg= github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0 h1:u2DAG8pk0xFH7TwS70t1gSZ/FtIIZWMSNyiu4SeXBYg= @@ -2672,8 +2672,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -3029,8 +3029,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -3048,8 +3048,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3071,8 +3071,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go index cae67bb78..5c006d5d8 100644 --- a/internal/dependencymanager/dependencyinstaller.go +++ b/internal/dependencymanager/dependencyinstaller.go @@ -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()) @@ -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)) @@ -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)) diff --git a/internal/super/flix.go b/internal/super/flix.go index bc3ad2b05..4df9858d4 100644 --- a/internal/super/flix.go +++ b/internal/super/flix.go @@ -23,6 +23,7 @@ import ( "fmt" "os" + "github.com/onflow/cadence/runtime/parser" "github.com/onflow/flixkit-go/v2/flixkit" "github.com/spf13/cobra" @@ -31,9 +32,12 @@ import ( "github.com/onflow/flowkit/v2/config" "github.com/onflow/flowkit/v2/output" + "golang.org/x/exp/slices" + "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 { @@ -57,13 +61,16 @@ type flixResult struct { result string } -var flags = flixFlags{} -var FlixCmd = &cobra.Command{ - Use: "flix", - Short: "execute, generate, package", - TraverseChildren: true, - GroupID: "tools", -} +var ( + flags = flixFlags{} + FlixCmd = &cobra.Command{ + Use: "flix", + Short: "Commands to execute, generate, package FLIX templates", + TraverseChildren: true, + GroupID: "tools", + Example: "flow flix execute transfer-flow 1 0x123456789", + } +) var executeCommand = &command.Command{ Cmd: &cobra.Command{ @@ -217,7 +224,10 @@ func generateFlixCmd( flags flixFlags, ) (result command.Result, err error) { cadenceFile := args[0] - depContracts := getDeployedContracts(state) + depContracts := getContractsFromState(state, flags.ExcludeNetworks) + if err != nil { + return nil, fmt.Errorf("could not get core contracts %w", err) + } if cadenceFile == "" { return nil, fmt.Errorf("no cadence code found") @@ -228,10 +238,6 @@ func generateFlixCmd( return nil, fmt.Errorf("could not read cadence file %s: %w", cadenceFile, err) } - if err != nil { - return nil, fmt.Errorf("could not create flix generator %w", err) - } - // get user's configured networks depNetworks := getNetworks(state) @@ -241,7 +247,7 @@ func generateFlixCmd( excludeMap[net] = true } - var filteredNetworks []config.Network + var filteredNetworks []flixkit.NetworkConfig for _, network := range depNetworks { if !excludeMap[network.Name] { filteredNetworks = append(filteredNetworks, network) @@ -254,8 +260,12 @@ func generateFlixCmd( } } - ctx := context.Background() + 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) @@ -265,7 +275,6 @@ func generateFlixCmd( flixQuery: cadenceFile, result: prettyJSON, }, err - } func (fr *flixResult) JSON() any { @@ -283,7 +292,40 @@ func (fr *flixResult) Oneliner() string { return fr.result } -func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos { +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) + } + + // 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") + } + + 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 ://
.%[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) + } + } + } + + return nil +} + +func getContractsFromState(state *flowkit.State, excludeNetworks []string) flixkit.ContractInfos { allContracts := make(flixkit.ContractInfos) depNetworks := make([]string, 0) accountAddresses := make(map[string]string) @@ -303,6 +345,9 @@ func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos { if _, ok := allContracts[c.Name]; !ok { allContracts[c.Name] = make(flixkit.NetworkAddressMap) } + if slices.Contains(excludeNetworks, d.Network) { + continue + } allContracts[c.Name][d.Network] = addr } } @@ -318,6 +363,9 @@ func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos { if _, ok := allContracts[c.Name]; !ok { allContracts[c.Name] = make(flixkit.NetworkAddressMap) } + if slices.Contains(excludeNetworks, network) { + continue + } allContracts[c.Name][network] = c.AccountAddress.HexWithPrefix() } locAliases := state.AliasesForNetwork(cfg) @@ -328,17 +376,37 @@ func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos { if _, ok := allContracts[name]; !ok { allContracts[name] = make(flixkit.NetworkAddressMap) } + if slices.Contains(excludeNetworks, network) { + continue + } allContracts[name][network] = addr } } + // add contracts that have not been deployed + for _, c := range *state.Contracts() { + if _, ok := allContracts[c.Name]; !ok { + allContracts[c.Name] = make(flixkit.NetworkAddressMap) + } + for _, alias := range c.Aliases { + if slices.Contains(excludeNetworks, alias.Network) { + continue + } + allContracts[c.Name][alias.Network] = alias.Address.HexWithPrefix() + } + } + return allContracts } -func getNetworks(state *flowkit.State) []config.Network { - networks := make([]config.Network, 0) +func getNetworks(state *flowkit.State) []flixkit.NetworkConfig { + networks := make([]flixkit.NetworkConfig, 0) for _, n := range *state.Networks() { - networks = append(networks, n) + networks = append(networks, flixkit.NetworkConfig{ + Name: n.Name, + Host: n.Host, + Key: n.Key, + }) } return networks } diff --git a/internal/super/flix_test.go b/internal/super/flix_test.go index 63a60811e..ceb363535 100644 --- a/internal/super/flix_test.go +++ b/internal/super/flix_test.go @@ -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" @@ -61,9 +62,15 @@ func (m *MockFlixService) GetTemplateAndReplaceImports(ctx context.Context, temp return result, nil } -func (m *MockFlixService) CreateTemplate(ctx context.Context, contractInfos flixkit.ContractInfos, code string, preFill string, networks []config.Network) (string, error) { - args := m.Called(ctx, contractInfos, code, preFill) - return TEMPLATE_STR, args.Error(1) +func (m *MockFlixService) CreateTemplate( + ctx context.Context, + contractInfos flixkit.ContractInfos, + code string, + preFill string, + networks []flixkit.NetworkConfig, +) (string, error) { + args := m.Called(ctx, contractInfos, code, preFill, networks) + return args.String(0), args.Error(1) } var JS_CODE = "export async function request() { const info = await fcl.query({ template: flixTemplate }); return info; }" @@ -144,6 +151,7 @@ func Test_PackageFlix(t *testing.T) { assert.NotNil(t, result) assert.Equal(t, JS_CODE, result.String()) } + func Test_GenerateFlix(t *testing.T) { srv := mocks.DefaultMockServices() cadenceFile := "cadence.cdc" @@ -167,11 +175,11 @@ func Test_GenerateFlix(t *testing.T) { }`) af := afero.Afero{Fs: afero.NewMemMapFs()} - err := afero.WriteFile(af.Fs, "flow.json", configJson, 0644) + err := afero.WriteFile(af.Fs, "flow.json", configJson, 0o644) assert.NoError(t, err) - err = afero.WriteFile(af.Fs, cadenceFile, []byte(cadenceCode), 0644) + err = afero.WriteFile(af.Fs, cadenceFile, []byte(cadenceCode), 0o644) assert.NoError(t, err) - err = afero.WriteFile(af.Fs, tests.ContractHelloString.Filename, []byte(tests.ContractHelloString.Source), 0644) + err = afero.WriteFile(af.Fs, tests.ContractHelloString.Filename, []byte(tests.ContractHelloString.Source), 0o644) assert.NoError(t, err) paths := []string{"flow.json"} state, err := flowkit.Load(paths, af) @@ -197,14 +205,32 @@ func Test_GenerateFlix(t *testing.T) { assert.Equal(t, 1, len(contracts)) logger := output.NewStdoutLogger(output.NoneLog) - contractInfos := make(flixkit.ContractInfos) - contractInfos[tests.ContractHelloString.Name] = make(flixkit.NetworkAddressMap) - contractInfos[tests.ContractHelloString.Name]["emulator"] = "0xf8d6e0586b0a20c7" - - ctx := context.Background() - mockFlixService.On("CreateTemplate", ctx, contractInfos, cadenceCode, "").Return(TEMPLATE_STR, nil) - - result, err := generateFlixCmd([]string{cadenceFile}, command.GlobalFlags{}, logger, srv.Mock, state, mockFlixService, flixFlags{}) + cInfos := make(flixkit.ContractInfos) + cInfos[tests.ContractHelloString.Name] = make(flixkit.NetworkAddressMap) + cInfos[tests.ContractHelloString.Name]["emulator"] = "0xf8d6e0586b0a20c7" + + mockFlixService.On( + "CreateTemplate", + mock.Anything, + mock.MatchedBy(func(contracts flixkit.ContractInfos) bool { + return len(contracts) == 1 + }), + cadenceCode, + "", + mock.MatchedBy(func(networks []flixkit.NetworkConfig) bool { + return len(networks) == 1 && networks[0].Name == config.EmulatorNetwork.Name + }), + ).Return(TEMPLATE_STR, nil) + + result, err := generateFlixCmd( + []string{cadenceFile}, + command.GlobalFlags{}, + logger, + srv.Mock, + state, + mockFlixService, + flixFlags{ExcludeNetworks: []string{"mainnet", "testnet"}}, + ) assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, TEMPLATE_STR, result.String()) @@ -216,17 +242,129 @@ func Test_GenerateFlixPrefill(t *testing.T) { templateName := "templateName" cadenceFile := "cadence.cdc" - var mockFS = afero.NewMemMapFs() - var rw = afero.Afero{Fs: mockFS} - err := rw.WriteFile(cadenceFile, []byte(CADENCE_SCRIPT), 0644) + mockFS := afero.NewMemMapFs() + rw := afero.Afero{Fs: mockFS} + err := rw.WriteFile(cadenceFile, []byte(CADENCE_SCRIPT), 0o644) assert.NoError(t, err) state, _ := flowkit.Init(rw) mockFlixService := new(MockFlixService) - ctx := context.Background() - mockFlixService.On("CreateTemplate", ctx, flixkit.ContractInfos{}, CADENCE_SCRIPT, templateName).Return(TEMPLATE_STR, nil) - - result, err := generateFlixCmd([]string{cadenceFile}, command.GlobalFlags{}, logger, srv.Mock, state, mockFlixService, flixFlags{PreFill: templateName}) + mockFlixService.On( + "CreateTemplate", + mock.Anything, + mock.MatchedBy(func(contracts flixkit.ContractInfos) bool { + return len(contracts) == 0 + }), + CADENCE_SCRIPT, + templateName, + mock.MatchedBy(func(networks []flixkit.NetworkConfig) bool { + return len(networks) == 2 + }), + ).Return(TEMPLATE_STR, nil) + + result, err := generateFlixCmd( + []string{cadenceFile}, + command.GlobalFlags{}, + logger, + srv.Mock, + state, + mockFlixService, + flixFlags{ + PreFill: templateName, + ExcludeNetworks: []string{"mainnet", "testnet"}, + }, + ) 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 ://
.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") +} diff --git a/internal/util/util.go b/internal/util/util.go index 20a95a3e4..111ed896c 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -28,8 +28,9 @@ import ( "text/tabwriter" "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" @@ -70,11 +71,11 @@ func AddToGitIgnore(filename string, loader flowkit.ReaderWriter) error { } // GetAddressNetwork returns the chain ID for an address. -func GetAddressNetwork(address flowsdk.Address) (flowsdk.ChainID, error) { - networks := []flowsdk.ChainID{ - flowsdk.Mainnet, - flowsdk.Testnet, - flowsdk.Emulator, +func GetAddressNetwork(address flow.Address) (flow.ChainID, error) { + networks := []flow.ChainID{ + flow.Mainnet, + flow.Testnet, + flow.Emulator, } for _, net := range networks { if address.IsValid(net) { @@ -175,3 +176,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 +}