From 680151ec74bea3838ea5bb01a5d72ffa1ba0fc1d Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 3 Dec 2024 11:27:55 -0600 Subject: [PATCH 1/8] update to use latest version of flixkit-go, pass in core contracts as dependencies --- go.mod | 12 ++--- go.sum | 24 +++++----- internal/super/flix.go | 90 ++++++++++++++++++++++++++++++------- internal/super/flix_test.go | 90 ++++++++++++++++++++++++++++--------- 4 files changed, 161 insertions(+), 55 deletions(-) diff --git a/go.mod b/go.mod index 56d7e7a7a..6bc3ba4a5 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.3.1 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 599730aaf..25a9bac10 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.3.1 h1:FfhMBAb78p6VAWkJ+iqdKLErGQVQgxk5w6DP5ZruWX8= @@ -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/super/flix.go b/internal/super/flix.go index bc3ad2b05..28df2ce60 100644 --- a/internal/super/flix.go +++ b/internal/super/flix.go @@ -24,7 +24,9 @@ import ( "os" "github.com/onflow/flixkit-go/v2/flixkit" + "github.com/onflow/flow-go/fvm/systemcontracts" + flowGo "github.com/onflow/flow-go/model/flow" "github.com/spf13/cobra" "github.com/onflow/flowkit/v2" @@ -34,6 +36,7 @@ import ( "github.com/onflow/flow-cli/internal/command" "github.com/onflow/flow-cli/internal/scripts" "github.com/onflow/flow-cli/internal/transactions" + "golang.org/x/exp/slices" ) type flixFlags struct { @@ -57,13 +60,15 @@ 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: "execute, generate, package", + TraverseChildren: true, + GroupID: "tools", + } +) var executeCommand = &command.Command{ Cmd: &cobra.Command{ @@ -218,6 +223,10 @@ func generateFlixCmd( ) (result command.Result, err error) { cadenceFile := args[0] depContracts := getDeployedContracts(state) + coreContracts, err := getCoreContracts(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 +237,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 +246,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) @@ -256,6 +261,13 @@ func generateFlixCmd( ctx := context.Background() + // merge deployed contracts and core contracts + for name, addresses := range coreContracts { + if _, exists := depContracts[name]; !exists { + depContracts[name] = addresses + } + } + 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 +277,6 @@ func generateFlixCmd( flixQuery: cadenceFile, result: prettyJSON, }, err - } func (fr *flixResult) JSON() any { @@ -283,6 +294,49 @@ func (fr *flixResult) Oneliner() string { return fr.result } +func getCoreContracts(excludeNetworks []string) (flixkit.ContractInfos, error) { + coreContracts := make(flixkit.ContractInfos) + + excludeMainnet := slices.Contains(excludeNetworks, config.MainnetNetwork.Name) + excludeTestnet := slices.Contains(excludeNetworks, config.TestnetNetwork.Name) + excludeEmulator := slices.Contains(excludeNetworks, config.EmulatorNetwork.Name) + + if !excludeMainnet { + // Add mainnet contracts + sc := systemcontracts.SystemContractsForChain(flowGo.Mainnet) + for _, c := range sc.All() { + if _, exists := coreContracts[c.Name]; !exists { + coreContracts[c.Name] = make(flixkit.NetworkAddressMap) + } + coreContracts[c.Name][config.MainnetNetwork.Name] = c.Address.HexWithPrefix() + } + } + + if !excludeTestnet { + // Add testnet contracts + cc := systemcontracts.SystemContractsForChain(flowGo.Testnet) + for _, c := range cc.All() { + if _, exists := coreContracts[c.Name]; !exists { + coreContracts[c.Name] = make(flixkit.NetworkAddressMap) + } + coreContracts[c.Name][config.TestnetNetwork.Name] = c.Address.HexWithPrefix() + } + } + + if !excludeEmulator { + // Add emulator contracts + em := systemcontracts.SystemContractsForChain(flowGo.Emulator) + for _, c := range em.All() { + if _, exists := coreContracts[c.Name]; !exists { + coreContracts[c.Name] = make(flixkit.NetworkAddressMap) + } + coreContracts[c.Name][config.EmulatorNetwork.Name] = c.Address.HexWithPrefix() + } + } + + return coreContracts, nil +} + func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos { allContracts := make(flixkit.ContractInfos) depNetworks := make([]string, 0) @@ -335,10 +389,14 @@ func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos { 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..7e297e954 100644 --- a/internal/super/flix_test.go +++ b/internal/super/flix_test.go @@ -61,9 +61,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 +150,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 +174,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 +204,33 @@ 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 { + // test more than 4 contracts, which will include core contracts + return len(contracts) > 4 + }), + 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,39 @@ 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 { + // test more than 4 contracts, which will include core contracts + return len(contracts) > 4 + }), + 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) } From 6d17b60702460b0cea32ce64f0bcc4577be64d74 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 3 Dec 2024 16:08:42 -0600 Subject: [PATCH 2/8] fix lint issue --- internal/super/flix.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/super/flix.go b/internal/super/flix.go index 28df2ce60..5c12421c0 100644 --- a/internal/super/flix.go +++ b/internal/super/flix.go @@ -33,10 +33,11 @@ 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" - "golang.org/x/exp/slices" ) type flixFlags struct { From 3bd330ec7363732a8cda6bf8f750df72d3abf272 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 3 Dec 2024 16:16:49 -0600 Subject: [PATCH 3/8] add example in usage, add abstraction for getting core contracts --- internal/super/flix.go | 46 ++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/internal/super/flix.go b/internal/super/flix.go index 5c12421c0..5c8d53aa5 100644 --- a/internal/super/flix.go +++ b/internal/super/flix.go @@ -25,8 +25,8 @@ import ( "github.com/onflow/flixkit-go/v2/flixkit" "github.com/onflow/flow-go/fvm/systemcontracts" + "github.com/onflow/flow-go/model/flow" - flowGo "github.com/onflow/flow-go/model/flow" "github.com/spf13/cobra" "github.com/onflow/flowkit/v2" @@ -65,9 +65,10 @@ var ( flags = flixFlags{} FlixCmd = &cobra.Command{ Use: "flix", - Short: "execute, generate, package", + Short: "Commands to execute, generate, package FLIX templates", TraverseChildren: true, GroupID: "tools", + Example: "flow flix execute transfer-flow 1 0x123456789", } ) @@ -298,40 +299,27 @@ func (fr *flixResult) Oneliner() string { func getCoreContracts(excludeNetworks []string) (flixkit.ContractInfos, error) { coreContracts := make(flixkit.ContractInfos) - excludeMainnet := slices.Contains(excludeNetworks, config.MainnetNetwork.Name) - excludeTestnet := slices.Contains(excludeNetworks, config.TestnetNetwork.Name) - excludeEmulator := slices.Contains(excludeNetworks, config.EmulatorNetwork.Name) - - if !excludeMainnet { - // Add mainnet contracts - sc := systemcontracts.SystemContractsForChain(flowGo.Mainnet) - for _, c := range sc.All() { - if _, exists := coreContracts[c.Name]; !exists { - coreContracts[c.Name] = make(flixkit.NetworkAddressMap) - } - coreContracts[c.Name][config.MainnetNetwork.Name] = c.Address.HexWithPrefix() - } + 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)}, } - if !excludeTestnet { - // Add testnet contracts - cc := systemcontracts.SystemContractsForChain(flowGo.Testnet) - for _, c := range cc.All() { - if _, exists := coreContracts[c.Name]; !exists { - coreContracts[c.Name] = make(flixkit.NetworkAddressMap) - } - coreContracts[c.Name][config.TestnetNetwork.Name] = c.Address.HexWithPrefix() + for _, nc := range networkConfigs { + if nc.excluded { + continue } - } - if !excludeEmulator { - // Add emulator contracts - em := systemcontracts.SystemContractsForChain(flowGo.Emulator) - for _, c := range em.All() { + contracts := systemcontracts.SystemContractsForChain(nc.chainID) + for _, c := range contracts.All() { if _, exists := coreContracts[c.Name]; !exists { coreContracts[c.Name] = make(flixkit.NetworkAddressMap) } - coreContracts[c.Name][config.EmulatorNetwork.Name] = c.Address.HexWithPrefix() + coreContracts[c.Name][nc.networkName] = c.Address.HexWithPrefix() } } From 77c1364b6861271779fed5968bb43125e9d645cd Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 3 Dec 2024 18:05:01 -0600 Subject: [PATCH 4/8] add user's dependencies from flow.json as deps for generating flix --- internal/super/flix.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/internal/super/flix.go b/internal/super/flix.go index 5c8d53aa5..92d7ed6ad 100644 --- a/internal/super/flix.go +++ b/internal/super/flix.go @@ -224,7 +224,7 @@ func generateFlixCmd( flags flixFlags, ) (result command.Result, err error) { cadenceFile := args[0] - depContracts := getDeployedContracts(state) + depContracts := getContractsFromState(state, flags.ExcludeNetworks) coreContracts, err := getCoreContracts(flags.ExcludeNetworks) if err != nil { return nil, fmt.Errorf("could not get core contracts %w", err) @@ -326,7 +326,7 @@ func getCoreContracts(excludeNetworks []string) (flixkit.ContractInfos, error) { return coreContracts, nil } -func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos { +func getContractsFromState(state *flowkit.State, excludeNetworks []string) flixkit.ContractInfos { allContracts := make(flixkit.ContractInfos) depNetworks := make([]string, 0) accountAddresses := make(map[string]string) @@ -346,6 +346,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 } } @@ -361,6 +364,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) @@ -371,10 +377,26 @@ 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.Dependencies() { + 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 } From 8234adb66920bdf2511b802d0923550252c4eada Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:26:32 -0800 Subject: [PATCH 5/8] Replace automatic core contract resolution from `flow flix generate` with dependency manager prompts (#1853) --- .../dependencymanager/dependencyinstaller.go | 15 +-- internal/super/flix.go | 61 ++++++------ internal/super/flix_test.go | 92 +++++++++++++++++++ internal/util/util.go | 13 +++ 4 files changed, 137 insertions(+), 44 deletions(-) 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 92d7ed6ad..4df9858d4 100644 --- a/internal/super/flix.go +++ b/internal/super/flix.go @@ -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" @@ -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 { @@ -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) } @@ -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) @@ -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 ://
.%[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 { @@ -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) } diff --git a/internal/super/flix_test.go b/internal/super/flix_test.go index 7e297e954..072575e16 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" @@ -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 ://
.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..562be06a3 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -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" @@ -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 +} \ No newline at end of file From 9c5bab2ecad5ab4ac0b45c4ca78cd0c4a098103c Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 6 Dec 2024 11:43:26 -0800 Subject: [PATCH 6/8] fix tests --- internal/super/flix_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/super/flix_test.go b/internal/super/flix_test.go index 072575e16..1c6e46c03 100644 --- a/internal/super/flix_test.go +++ b/internal/super/flix_test.go @@ -213,8 +213,7 @@ func Test_GenerateFlix(t *testing.T) { "CreateTemplate", mock.Anything, mock.MatchedBy(func(contracts flixkit.ContractInfos) bool { - // test more than 4 contracts, which will include core contracts - return len(contracts) > 4 + return len(contracts) == 1 }), cadenceCode, "", @@ -254,8 +253,7 @@ func Test_GenerateFlixPrefill(t *testing.T) { "CreateTemplate", mock.Anything, mock.MatchedBy(func(contracts flixkit.ContractInfos) bool { - // test more than 4 contracts, which will include core contracts - return len(contracts) > 4 + return len(contracts) == 0 }), CADENCE_SCRIPT, templateName, From b65a17a1c1d60bed62a25752fd45f8970f3dbca7 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 6 Dec 2024 12:41:32 -0800 Subject: [PATCH 7/8] fix test --- internal/util/util.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/util/util.go b/internal/util/util.go index 562be06a3..2f22f5fb6 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -28,7 +28,6 @@ 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" @@ -72,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) { From ba7b9bbd44978cef779a78931ec6226d37205c7a Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 6 Dec 2024 12:51:56 -0800 Subject: [PATCH 8/8] format --- internal/super/flix_test.go | 4 ++-- internal/util/util.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/super/flix_test.go b/internal/super/flix_test.go index 1c6e46c03..ceb363535 100644 --- a/internal/super/flix_test.go +++ b/internal/super/flix_test.go @@ -348,8 +348,8 @@ func Test_GenerateFlixMissingAlias(t *testing.T) { assert.NoError(t, err) state, _ := flowkit.Init(rw) state.Contracts().AddOrUpdate(config.Contract{ - Name: "Foobar", - Aliases: []config.Alias{{Address: flow.Address{0x01}, Network: "mainnet"}}, + Name: "Foobar", + Aliases: []config.Alias{{Address: flow.Address{0x01}, Network: "mainnet"}}, }) mockFlixService := new(MockFlixService) diff --git a/internal/util/util.go b/internal/util/util.go index 2f22f5fb6..111ed896c 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -186,4 +186,4 @@ func IsCoreContract(contractName string) bool { } } return false -} \ No newline at end of file +}