Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions internal/dependencymanager/dependencyinstaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
107 changes: 107 additions & 0 deletions internal/dependencymanager/dependencyinstaller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})
}
9 changes: 8 additions & 1 deletion internal/dependencymanager/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down