diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go index 8df6e16d1..80ff47753 100644 --- a/internal/dependencymanager/dependencyinstaller.go +++ b/internal/dependencymanager/dependencyinstaller.go @@ -103,6 +103,7 @@ type DependencyFlags struct { skipAlias bool `default:"false" flag:"skip-alias" info:"Skip prompting for an alias"` skipUpdatePrompts bool `default:"false" flag:"skip-update-prompts" info:"Skip prompting to update existing dependencies"` deploymentAccount string `default:"" flag:"deployment-account,d" info:"Account name to use for deployments (skips deployment account prompt)"` + name string `default:"" flag:"name" info:"Import alias name for the dependency (sets canonical field for Cadence import aliasing)"` } func (f *DependencyFlags) AddToCommand(cmd *cobra.Command) { @@ -125,6 +126,7 @@ type DependencyInstaller struct { SkipDeployments bool SkipAlias bool DeploymentAccount string + Name string logs categorizedLogs dependencies map[string]config.Dependency accountAliases map[string]map[string]flowsdk.Address // network -> account -> alias @@ -164,6 +166,7 @@ func NewDependencyInstaller(logger output.Logger, state *flowkit.State, saveStat SkipDeployments: flags.skipDeployments, SkipAlias: flags.skipAlias, DeploymentAccount: flags.deploymentAccount, + Name: flags.name, dependencies: make(map[string]config.Dependency), logs: categorizedLogs{}, accountAliases: make(map[string]map[string]flowsdk.Address), @@ -222,6 +225,13 @@ func (di *DependencyInstaller) AddBySourceString(depSource string) error { }, } + // If a name is provided, use it as the import alias and set canonical for Cadence import aliasing + // This enables "import OriginalContract as AliasName from address" syntax + if di.Name != "" { + dep.Name = di.Name + dep.Canonical = depContractName + } + return di.Add(dep) } @@ -257,6 +267,13 @@ func (di *DependencyInstaller) AddByCoreContractName(coreContractName string) er }, } + // If a name is provided, use it as the import alias and set canonical for Cadence import aliasing + // This enables "import OriginalContract as AliasName from address" syntax + if di.Name != "" { + dep.Name = di.Name + dep.Canonical = depContractName + } + return di.Add(dep) } @@ -275,6 +292,12 @@ func (di *DependencyInstaller) AddByDefiContractName(defiContractName string) er return fmt.Errorf("contract %s not found in DeFi actions contracts", defiContractName) } + // If a custom name is provided, use it as the dependency name and set canonical + if di.Name != "" { + targetDep.Name = di.Name + targetDep.Canonical = defiContractName + } + return di.Add(*targetDep) } @@ -333,6 +356,11 @@ func (di *DependencyInstaller) AddMany(dependencies []config.Dependency) error { } func (di *DependencyInstaller) AddAllByNetworkAddress(sourceStr string) error { + // Check if name flag is set - not supported when installing all contracts at an address + if di.Name != "" { + return fmt.Errorf("--name flag is not supported when installing all contracts at an address (network://address). Please specify a specific contract using network://address.ContractName format") + } + network, address := ParseNetworkAddressString(sourceStr) accountContracts, err := di.getContracts(network, flowsdk.HexToAddress(address)) @@ -783,12 +811,13 @@ func (di *DependencyInstaller) updateDependencyAlias(contractName, aliasNetwork } func (di *DependencyInstaller) updateDependencyState(originalDependency config.Dependency, contractHash string) error { - // Create the dependency to save, preserving aliases from the original + // Create the dependency to save, preserving aliases and canonical from the original dep := config.Dependency{ - Name: originalDependency.Name, - Source: originalDependency.Source, - Hash: contractHash, - Aliases: originalDependency.Aliases, // Preserve aliases from the original dependency + Name: originalDependency.Name, + Source: originalDependency.Source, + Hash: contractHash, + Aliases: originalDependency.Aliases, + Canonical: originalDependency.Canonical, } isNewDep := di.State.Dependencies().ByName(dep.Name) == nil diff --git a/internal/dependencymanager/dependencyinstaller_test.go b/internal/dependencymanager/dependencyinstaller_test.go index f2f1ba1a5..8e099c8e8 100644 --- a/internal/dependencymanager/dependencyinstaller_test.go +++ b/internal/dependencymanager/dependencyinstaller_test.go @@ -743,3 +743,110 @@ func TestAliasedImportHandling(t *testing.T) { assert.NotNil(t, fileContent) }) } + +func TestDependencyInstallerWithAlias(t *testing.T) { + logger := output.NewStdoutLogger(output.NoneLog) + _, state, _ := util.TestMocks(t) + + serviceAcc, _ := state.EmulatorServiceAccount() + serviceAddress := serviceAcc.Address + + t.Run("AddBySourceStringWithName", func(t *testing.T) { + gw := mocks.DefaultMockGateway() + + gw.GetAccount.Run(func(args mock.Arguments) { + addr := args.Get(1).(flow.Address) + assert.Equal(t, addr.String(), serviceAddress.String()) + acc := tests.NewAccountWithAddress(addr.String()) + acc.Contracts = map[string][]byte{ + "NumberFormatter": []byte("access(all) contract NumberFormatter {}"), + } + gw.GetAccount.Return(acc, nil) + }) + + di := &DependencyInstaller{ + Gateways: map[string]gateway.Gateway{ + config.EmulatorNetwork.Name: gw.Mock, + }, + Logger: logger, + State: state, + SaveState: true, + TargetDir: "", + SkipDeployments: true, + SkipAlias: true, + Name: "NumberFormatterCustom", + dependencies: make(map[string]config.Dependency), + } + + err := di.AddBySourceString(fmt.Sprintf("%s://%s.%s", config.EmulatorNetwork.Name, serviceAddress.String(), "NumberFormatter")) + assert.NoError(t, err, "Failed to add dependency with import alias") + + // Check that the dependency was added with the import alias name + dep := state.Dependencies().ByName("NumberFormatterCustom") + assert.NotNil(t, dep, "Dependency should exist with import alias name") + assert.Equal(t, "NumberFormatter", dep.Source.ContractName, "Source ContractName should be the actual contract name") + assert.Equal(t, "NumberFormatter", dep.Canonical, "Canonical should be set to the actual contract name for import aliasing") + + // Check that the contract was added with canonical field for Cadence import aliasing + contract, err := state.Contracts().ByName("NumberFormatterCustom") + assert.NoError(t, err, "Contract should exist") + assert.Equal(t, "NumberFormatter", contract.Canonical, "Contract Canonical should be set for import aliasing") + + // Check that the file was created with the actual contract name + filePath := fmt.Sprintf("imports/%s/NumberFormatter.cdc", serviceAddress.String()) + fileContent, err := state.ReaderWriter().ReadFile(filePath) + assert.NoError(t, err, "Contract file should exist at imports/address/NumberFormatter.cdc") + assert.NotNil(t, fileContent) + }) + + t.Run("AddByCoreContractNameWithName", func(t *testing.T) { + // Mock the gateway to return FlowToken contract + gw := mocks.DefaultMockGateway() + gw.GetAccount.Run(func(args mock.Arguments) { + addr := args.Get(1).(flow.Address) + acc := tests.NewAccountWithAddress(addr.String()) + acc.Contracts = map[string][]byte{ + "FlowToken": []byte("access(all) contract FlowToken {}"), + } + gw.GetAccount.Return(acc, nil) + }) + + di := &DependencyInstaller{ + Gateways: map[string]gateway.Gateway{ + config.MainnetNetwork.Name: gw.Mock, + }, + Logger: logger, + State: state, + SaveState: true, + TargetDir: "", + SkipDeployments: true, + SkipAlias: true, + Name: "FlowTokenCustom", + dependencies: make(map[string]config.Dependency), + } + + err := di.AddByCoreContractName("FlowToken") + assert.NoError(t, err, "Failed to add core contract with import alias") + + // Check that the dependency was added with the import alias name + dep := state.Dependencies().ByName("FlowTokenCustom") + assert.NotNil(t, dep, "Dependency should exist with import alias name") + assert.Equal(t, "FlowToken", dep.Source.ContractName, "Source ContractName should be FlowToken") + assert.Equal(t, "FlowToken", dep.Canonical, "Canonical should be set to FlowToken for import aliasing") + }) + + t.Run("AddAllByNetworkAddressWithNameError", func(t *testing.T) { + // This test doesn't need gateways since it returns an error before making any gateway calls + di := &DependencyInstaller{ + Logger: logger, + State: state, + SaveState: true, + TargetDir: "", + Name: "SomeName", + } + + err := di.AddAllByNetworkAddress(fmt.Sprintf("%s://%s", config.EmulatorNetwork.Name, serviceAddress.String())) + assert.Error(t, err, "Should error when using --name with network://address format") + assert.Contains(t, err.Error(), "--name flag is not supported when installing all contracts", "Error message should mention name flag limitation") + }) +} diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go index a8e2ae63d..c180634ad 100644 --- a/internal/dependencymanager/install.go +++ b/internal/dependencymanager/install.go @@ -74,10 +74,16 @@ Examples: flow dependencies install --deployment-account my-account FlowToken flow dependencies install -d my-account FlowToken + 8. Install a dependency with an import alias (for Cadence import aliasing): + flow dependencies install --name USDF testnet://0x1234abcd.FiatToken + flow dependencies install --name MyToken mainnet://0xabcd1234.TokenContract + This creates an import alias that enables "import FiatToken as USDF from 0x1234abcd" syntax in Cadence. + Flags: • --deployment-account, -d: Specify the account name to use for deployments (skips deployment account prompt) • --skip-deployments: Skip adding the dependency to deployments • --skip-alias: Skip prompting for an alias +• --name: Import alias name for the dependency (sets canonical field for Cadence import aliasing, e.g., --name USDF) Note: • Using 'network://address' will attempt to install all contracts deployed at that address. @@ -94,7 +100,8 @@ flow dependencies install FlowToken flow dependencies install DeFiActions flow dependencies install FlowToken NonFungibleToken DeFiActions flow dependencies install --deployment-account my-account FlowToken -flow dependencies install -d my-account FlowToken`, +flow dependencies install -d my-account FlowToken +flow dependencies install --name USDF testnet://0x1234abcd.FiatToken`, Args: cobra.ArbitraryArgs, }, Flags: &installFlags,