From c61390ded8553b2335398f2909ce849c3cdd9027 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:41:32 -0800 Subject: [PATCH 1/6] Refactor to get contracts method --- .../dependencymanager/dependencyinstaller.go | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go index 5c006d5d8..b933e70a9 100644 --- a/internal/dependencymanager/dependencyinstaller.go +++ b/internal/dependencymanager/dependencyinstaller.go @@ -285,6 +285,29 @@ func (di *DependencyInstaller) processDependency(dependency config.Dependency) e return di.fetchDependencies(dependency.Source.NetworkName, depAddress, dependency.Name, dependency.Source.ContractName) } +func (di *DependencyInstaller) getContracts(network string, address flowsdk.Address) (map[string][]byte, error) { + gw, ok := di.Gateways[network] + if !ok { + return nil, fmt.Errorf("gateway for network %s not found", network) + } + + ctx := context.Background() + acct, err := gw.GetAccount(ctx, address) + if err != nil { + return nil, fmt.Errorf("failed to get account at %s on %s: %w", address, network, err) + } + + if acct == nil { + return nil, fmt.Errorf("no account found at address %s on network %s", address, network) + } + + if len(acct.Contracts) == 0 { + return nil, fmt.Errorf("no contracts found at address %s on network %s", address, network) + } + + return acct.Contracts, nil +} + func (di *DependencyInstaller) fetchDependencies(networkName string, address flowsdk.Address, assignedName, contractName string) error { sourceString := fmt.Sprintf("%s://%s.%s", networkName, address.String(), contractName) @@ -304,20 +327,12 @@ func (di *DependencyInstaller) fetchDependencies(networkName string, address flo return fmt.Errorf("error adding dependency: %w", err) } - ctx := context.Background() - account, err := di.Gateways[networkName].GetAccount(ctx, address) + accountContracts, err := di.getContracts(networkName, address) if err != nil { - return fmt.Errorf("failed to get account: %w", err) - } - if account == nil { - return fmt.Errorf("account is nil for address: %s", address) - } - - if account.Contracts == nil { - return fmt.Errorf("contracts are nil for account: %s", address) + return fmt.Errorf("error fetching contracts: %w", err) } - contract, ok := account.Contracts[contractName] + contract, ok := accountContracts[contractName] if !ok { return fmt.Errorf("contract %s not found at address %s", contractName, address.String()) } From 28b70693708360f05f24b3fb6dcda483f2276193 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:33:55 -0800 Subject: [PATCH 2/6] Add AddAllByNetworkAddress --- .../dependencymanager/dependencyinstaller.go | 73 ++++++++++++++----- internal/dependencymanager/install.go | 28 +++++++ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go index b933e70a9..bc6a5cae9 100644 --- a/internal/dependencymanager/dependencyinstaller.go +++ b/internal/dependencymanager/dependencyinstaller.go @@ -257,6 +257,48 @@ func (di *DependencyInstaller) AddMany(dependencies []config.Dependency) error { return nil } +func (di *DependencyInstaller) AddAllByNetworkAddress(sourceStr string) error { + network, address := ParseNetworkAddressString(sourceStr) + + accountContracts, err := di.getContracts(network, flowsdk.HexToAddress(address)) + if err != nil { + return fmt.Errorf("failed to fetch account contracts: %w", err) + } + + for _, contract := range accountContracts { + program, err := project.NewProgram(contract, nil, "") + if err != nil { + return fmt.Errorf("failed to parse program: %w", err) + } + + contractName, err := program.Name() + if err != nil { + return fmt.Errorf("failed to parse contract name: %w", err) + } + + if err := di.handleFoundContract(network, address, contractName, program); err != nil { + return fmt.Errorf("failed to handle found contract: %w", err) + } + + if program.HasAddressImports() { + imports := program.AddressImportDeclarations() + for _, imp := range imports { + contractName := imp.Identifiers[0].String() + err := di.fetchDependencies(network, flowsdk.HexToAddress(imp.Location.String()), contractName) + if err != nil { + return err + } + } + } + } + + if err := di.saveState(); err != nil { + return err + } + + return nil +} + func (di *DependencyInstaller) addDependency(dep config.Dependency) error { sourceString := fmt.Sprintf("%s://%s.%s", dep.Source.NetworkName, dep.Source.Address.String(), dep.Source.ContractName) @@ -282,7 +324,7 @@ func (di *DependencyInstaller) checkForConflictingContracts() { func (di *DependencyInstaller) processDependency(dependency config.Dependency) error { depAddress := flowsdk.HexToAddress(dependency.Source.Address.String()) - return di.fetchDependencies(dependency.Source.NetworkName, depAddress, dependency.Name, dependency.Source.ContractName) + return di.fetchDependencies(dependency.Source.NetworkName, depAddress, dependency.Source.ContractName) } func (di *DependencyInstaller) getContracts(network string, address flowsdk.Address) (map[string][]byte, error) { @@ -308,7 +350,7 @@ func (di *DependencyInstaller) getContracts(network string, address flowsdk.Addr return acct.Contracts, nil } -func (di *DependencyInstaller) fetchDependencies(networkName string, address flowsdk.Address, assignedName, contractName string) error { +func (di *DependencyInstaller) fetchDependencies(networkName string, address flowsdk.Address, contractName string) error { sourceString := fmt.Sprintf("%s://%s.%s", networkName, address.String(), contractName) if _, exists := di.dependencies[sourceString]; exists { @@ -316,7 +358,7 @@ func (di *DependencyInstaller) fetchDependencies(networkName string, address flo } err := di.addDependency(config.Dependency{ - Name: assignedName, + Name: contractName, Source: config.Source{ NetworkName: networkName, Address: address, @@ -342,16 +384,7 @@ func (di *DependencyInstaller) fetchDependencies(networkName string, address flo return fmt.Errorf("failed to parse program: %w", err) } - parsedContractName, err := program.Name() - if err != nil { - return fmt.Errorf("failed to parse contract name: %w", err) - } - - if parsedContractName != contractName { - return fmt.Errorf("contract name mismatch: expected %s, got %s", contractName, parsedContractName) - } - - if err := di.handleFoundContract(networkName, address.String(), assignedName, contractName, program); err != nil { + if err := di.handleFoundContract(networkName, address.String(), contractName, program); err != nil { return fmt.Errorf("failed to handle found contract: %w", err) } @@ -359,7 +392,7 @@ func (di *DependencyInstaller) fetchDependencies(networkName string, address flo imports := program.AddressImportDeclarations() for _, imp := range imports { contractName := imp.Identifiers[0].String() - err := di.fetchDependencies(networkName, flowsdk.HexToAddress(imp.Location.String()), contractName, contractName) + err := di.fetchDependencies(networkName, flowsdk.HexToAddress(imp.Location.String()), contractName) if err != nil { return err } @@ -407,7 +440,7 @@ func (di *DependencyInstaller) handleFileSystem(contractAddr, contractName, cont return nil } -func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, assignedName, contractName string, program *project.Program) error { +func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, contractName string, program *project.Program) error { hash := sha256.New() hash.Write(program.CodeWithUnprocessedImports()) originalContractDataHash := hex.EncodeToString(hash.Sum(nil)) @@ -415,11 +448,11 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as program.ConvertAddressImports() contractData := string(program.CodeWithUnprocessedImports()) - dependency := di.State.Dependencies().ByName(assignedName) + dependency := di.State.Dependencies().ByName(contractName) // If a dependency by this name already exists and its remote source network or address does not match, then give option to stop or continue if dependency != nil && (dependency.Source.NetworkName != networkName || dependency.Source.Address.String() != contractAddr) { - di.Logger.Info(fmt.Sprintf("%s A dependency named %s already exists with a different remote source. Please fix the conflict and retry.", util.PrintEmoji("🚫"), assignedName)) + di.Logger.Info(fmt.Sprintf("%s A dependency named %s already exists with a different remote source. Please fix the conflict and retry.", util.PrintEmoji("🚫"), contractName)) os.Exit(0) return nil } @@ -434,7 +467,7 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as } } - err := di.updateDependencyState(networkName, contractAddr, assignedName, contractName, originalContractDataHash) + err := di.updateDependencyState(networkName, contractAddr, contractName, originalContractDataHash) if err != nil { di.Logger.Error(fmt.Sprintf("Error updating state: %v", err)) return err @@ -535,9 +568,9 @@ func (di *DependencyInstaller) updateDependencyAlias(contractName, aliasNetwork return nil } -func (di *DependencyInstaller) updateDependencyState(networkName, contractAddress, assignedName, contractName, contractHash string) error { +func (di *DependencyInstaller) updateDependencyState(networkName, contractAddress, contractName, contractHash string) error { dep := config.Dependency{ - Name: assignedName, + Name: contractName, Source: config.Source{ NetworkName: networkName, Address: flowsdk.HexToAddress(contractAddress), diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go index 87e648aa5..c846546e1 100644 --- a/internal/dependencymanager/install.go +++ b/internal/dependencymanager/install.go @@ -78,6 +78,19 @@ func install( continue } + // Check if the dependency is in the "network://address" format (address only) + hasContract, err := hasContractName(dep) + if err != nil { + return nil, fmt.Errorf("invalid dependency format") + } + + if !hasContract { + if err := installer.AddAllByNetworkAddress(dep); err != nil { + logger.Error(fmt.Sprintf("Error adding contracts by address: %v", err)) + return nil, err + } + } + if err := installer.AddBySourceString(dep); err != nil { if strings.Contains(err.Error(), "invalid dependency source format") { logger.Error(fmt.Sprintf("Error: '%s' is neither a core contract nor a valid dependency source format.\nPlease provide a valid dependency source in the format 'network://address.ContractName', e.g., 'testnet://0x1234567890abcdef.MyContract', or use a valid core contract name such as 'FlowToken'.", dep)) @@ -120,3 +133,18 @@ func findCoreContractCaseInsensitive(name string) string { } return "" } + +// Check if the input is in "network://address" or "network://address.contract" format +func hasContractName(dep string) (bool, error) { + parts := strings.SplitN(dep, "://", 2) + if len(parts) != 2 { + return false, fmt.Errorf("invalid format: missing '://'") + } + + return strings.Contains(parts[1], "."), nil +} + +func ParseNetworkAddressString(sourceStr string) (network, address string) { + parts := strings.Split(sourceStr, "://") + return parts[0], parts[1] +} From 840166aa06edff1c517d8f9b61271a7871a5d7bc Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:49:24 -0800 Subject: [PATCH 3/6] Use existing AddMany --- .../dependencymanager/dependencyinstaller.go | 24 +++++++++---------- internal/dependencymanager/install.go | 16 ++++++------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go index bc6a5cae9..2b8155d9e 100644 --- a/internal/dependencymanager/dependencyinstaller.go +++ b/internal/dependencymanager/dependencyinstaller.go @@ -265,6 +265,8 @@ func (di *DependencyInstaller) AddAllByNetworkAddress(sourceStr string) error { return fmt.Errorf("failed to fetch account contracts: %w", err) } + var dependencies []config.Dependency + for _, contract := range accountContracts { program, err := project.NewProgram(contract, nil, "") if err != nil { @@ -276,23 +278,19 @@ func (di *DependencyInstaller) AddAllByNetworkAddress(sourceStr string) error { return fmt.Errorf("failed to parse contract name: %w", err) } - if err := di.handleFoundContract(network, address, contractName, program); err != nil { - return fmt.Errorf("failed to handle found contract: %w", err) + dep := config.Dependency{ + Name: contractName, + Source: config.Source{ + NetworkName: network, + Address: flowsdk.HexToAddress(address), + ContractName: contractName, + }, } - if program.HasAddressImports() { - imports := program.AddressImportDeclarations() - for _, imp := range imports { - contractName := imp.Identifiers[0].String() - err := di.fetchDependencies(network, flowsdk.HexToAddress(imp.Location.String()), contractName) - if err != nil { - return err - } - } - } + dependencies = append(dependencies, dep) } - if err := di.saveState(); err != nil { + if err := di.AddMany(dependencies); err != nil { return err } diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go index c846546e1..0d93d8680 100644 --- a/internal/dependencymanager/install.go +++ b/internal/dependencymanager/install.go @@ -89,15 +89,15 @@ func install( logger.Error(fmt.Sprintf("Error adding contracts by address: %v", err)) return nil, err } - } - - if err := installer.AddBySourceString(dep); err != nil { - if strings.Contains(err.Error(), "invalid dependency source format") { - logger.Error(fmt.Sprintf("Error: '%s' is neither a core contract nor a valid dependency source format.\nPlease provide a valid dependency source in the format 'network://address.ContractName', e.g., 'testnet://0x1234567890abcdef.MyContract', or use a valid core contract name such as 'FlowToken'.", dep)) - } else { - logger.Error(fmt.Sprintf("Error adding dependency %s: %v", dep, err)) + } else { + if err := installer.AddBySourceString(dep); err != nil { + if strings.Contains(err.Error(), "invalid dependency source format") { + logger.Error(fmt.Sprintf("Error: '%s' is neither a core contract nor a valid dependency source format.\nPlease provide a valid dependency source in the format 'network://address.ContractName', e.g., 'testnet://0x1234567890abcdef.MyContract', or use a valid core contract name such as 'FlowToken'.", dep)) + } else { + logger.Error(fmt.Sprintf("Error adding dependency %s: %v", dep, err)) + } + return nil, err } - return nil, err } } From 211ac819e0cabe987cfcadf29a923cc2ac1e9634 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:52:22 -0800 Subject: [PATCH 4/6] Check for conflicts --- internal/dependencymanager/dependencyinstaller.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go index 2b8155d9e..c7cce1a41 100644 --- a/internal/dependencymanager/dependencyinstaller.go +++ b/internal/dependencymanager/dependencyinstaller.go @@ -250,6 +250,8 @@ func (di *DependencyInstaller) AddMany(dependencies []config.Dependency) error { } } + di.checkForConflictingContracts() + if err := di.saveState(); err != nil { return err } From 3f6acb6124e1529ad4da0e96a9a5d02300155d5c Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:24:15 -0800 Subject: [PATCH 5/6] Update use --- internal/dependencymanager/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go index 0d93d8680..08e0ea52a 100644 --- a/internal/dependencymanager/install.go +++ b/internal/dependencymanager/install.go @@ -39,7 +39,7 @@ var installFlags = Flags{} var installCommand = &command.Command{ Cmd: &cobra.Command{ - Use: "install", + Use: "install [dependencies]", Short: "Install contract and dependencies.", Example: `flow dependencies install flow dependencies install testnet://0afe396ebc8eee65.FlowToken From 645a67d22b90724a9f9d3ef95cc82883a8dd22d4 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:26:27 -0800 Subject: [PATCH 6/6] Improve long description --- internal/dependencymanager/install.go | 37 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go index 08e0ea52a..b1352b454 100644 --- a/internal/dependencymanager/install.go +++ b/internal/dependencymanager/install.go @@ -39,10 +39,41 @@ var installFlags = Flags{} var installCommand = &command.Command{ Cmd: &cobra.Command{ - Use: "install [dependencies]", - Short: "Install contract and dependencies.", + Use: "install [dependencies...]", + Short: "Install contracts and their dependencies.", + Long: `Install Flow contracts and their dependencies. + +By default, this command will install any dependencies listed in the flow.json file at the root of your project. +You can also specify one or more dependencies directly on the command line, using any of the following formats: + + • network://address + • network://address.ContractName + • core contract name (e.g., FlowToken, NonFungibleToken) + +Examples: + 1. Install dependencies listed in flow.json: + flow dependencies install + + 2. Install a specific core contract by name: + flow dependencies install FlowToken + + 3. Install a single contract by network and address (all contracts at that address): + flow dependencies install testnet://0x1234abcd + + 4. Install a specific contract by network, address, and contract name: + flow dependencies install testnet://0x1234abcd.MyContract + + 5. Install multiple dependencies: + flow dependencies install FungibleToken NonFungibleToken + +Note: +• Using 'network://address' will attempt to install all contracts deployed at that address. +• Using 'network://address.ContractName' will install only the specified contract. +• Specifying a known core contract (e.g., FlowToken) will install it from the official system contracts + address on Mainnet or Testnet (depending on your project's default network). +`, Example: `flow dependencies install -flow dependencies install testnet://0afe396ebc8eee65.FlowToken +flow dependencies install testnet://0x7e60df042a9c0868.FlowToken flow dependencies install FlowToken flow dependencies install FlowToken NonFungibleToken`, Args: cobra.ArbitraryArgs,