diff --git a/config/contract.go b/config/contract.go index 5e781f73..b693dade 100644 --- a/config/contract.go +++ b/config/contract.go @@ -211,6 +211,20 @@ func (c *Contracts) AddDependencyAsContract(dependency Dependency, networkName s }) } } + // Also add any custom network aliases from the dependency (e.g., mainnet-fork) + for _, depAlias := range dependency.Aliases { + // Skip if already added by supportedNetworks() + alreadyExists := false + for _, existing := range aliases { + if existing.Network == depAlias.Network { + alreadyExists = true + break + } + } + if !alreadyExists { + aliases = append(aliases, depAlias) + } + } } else { aliases = append(aliases, dependency.Aliases...) } diff --git a/config/json/network.go b/config/json/network.go index f6dda339..b41a6d7b 100644 --- a/config/json/network.go +++ b/config/json/network.go @@ -37,16 +37,18 @@ func (j jsonNetworks) transformToConfig() (config.Networks, error) { networks := make(config.Networks, 0) for networkName, n := range j { - if n.Advanced.Key != "" && n.Advanced.Host != "" { - err := validateECDSAP256Pub(n.Advanced.Key) - if err != nil { - return nil, fmt.Errorf("invalid key %s for network with name %s", n.Advanced.Key, networkName) + // Advanced form: host required, key optional, fork optional + if n.Advanced.Host != "" || n.Advanced.Fork != "" { + if n.Advanced.Key != "" { + if err := validateECDSAP256Pub(n.Advanced.Key); err != nil { + return nil, fmt.Errorf("invalid key %s for network with name %s", n.Advanced.Key, networkName) + } } - networks = append(networks, config.Network{ Name: networkName, Host: n.Advanced.Host, Key: n.Advanced.Key, + Fork: n.Advanced.Fork, }) } else if n.Simple.Host != "" { networks = append(networks, config.Network{ @@ -66,7 +68,8 @@ func transformNetworksToJSON(networks config.Networks) jsonNetworks { jsonNetworks := jsonNetworks{} for _, n := range networks { - if n.Key != "" { + // Use advanced when key or fork present; otherwise simple + if n.Key != "" || n.Fork != "" { jsonNetworks[n.Name] = transformAdvancedNetworkToJSON(n) } else { jsonNetworks[n.Name] = transformSimpleNetworkToJSON(n) @@ -89,6 +92,7 @@ func transformAdvancedNetworkToJSON(n config.Network) jsonNetwork { Advanced: advancedNetwork{ Host: n.Host, Key: n.Key, + Fork: n.Fork, }, } } @@ -103,8 +107,9 @@ type simpleNetwork struct { } type advancedNetwork struct { - Host string `json:"host"` - Key string `json:"key"` + Host string `json:"host,omitempty"` + Key string `json:"key,omitempty"` + Fork string `json:"fork,omitempty"` } func (j *jsonNetwork) UnmarshalJSON(b []byte) error { @@ -121,6 +126,7 @@ func (j *jsonNetwork) UnmarshalJSON(b []byte) error { if err == nil { j.Advanced.Host = advanced.Host j.Advanced.Key = advanced.Key + j.Advanced.Fork = advanced.Fork } return err diff --git a/config/json/network_test.go b/config/json/network_test.go index 6cd24c68..28e91629 100644 --- a/config/json/network_test.go +++ b/config/json/network_test.go @@ -125,14 +125,14 @@ func Test_TransformConfigAdvanced(t *testing.T) { assert.Equal(t, "access.testnet.nodes.onflow.org:9000", testnet.Host) assert.Equal(t, "5000676131ad3e22d853a3f75a5b5d0db4236d08dd6612e2baad771014b5266a242bccecc3522ff7207ac357dbe4f225c709d9b273ac484fed5d13976a39bdcd", testnet.Key) }) - t.Run("should return error if advanced config does not have key", func(t *testing.T) { + t.Run("should allow advanced config without key", func(t *testing.T) { b := []byte(`{"testnet":{"host":"access.testnet.nodes.onflow.org:9000"}}`) var jsonNetworks jsonNetworks err := json.Unmarshal(b, &jsonNetworks) assert.NoError(t, err) _, err = jsonNetworks.transformToConfig() - assert.Error(t, err) + assert.NoError(t, err) }) t.Run("should return error if advanced config does not have host", func(t *testing.T) { b := []byte(`{"testnet":{"key": "5000676131ad3e22d853a3f75a5b5d0db4236d08dd6612e2baad771014b5266a242bccecc3522ff7207ac357dbe4f225c709d9b273ac484fed5d13976a39bdcd"}}`) diff --git a/config/network.go b/config/network.go index 658a79f0..d247783a 100644 --- a/config/network.go +++ b/config/network.go @@ -55,6 +55,7 @@ type Network struct { Name string Host string Key string + Fork string // Source network for alias resolution (e.g., "mainnet" for forked networks) } // ByName get network by name or return an error if not found. diff --git a/schema.json b/schema.json index ef122f51..f6276b4d 100644 --- a/schema.json +++ b/schema.json @@ -105,14 +105,13 @@ }, "key": { "type": "string" + }, + "fork": { + "type": "string" } }, "additionalProperties": false, - "type": "object", - "required": [ - "host", - "key" - ] + "type": "object" }, "contractDeployment": { "properties": { diff --git a/state.go b/state.go index 00245caa..201b8041 100644 --- a/state.go +++ b/state.go @@ -164,7 +164,7 @@ func (p *State) SetEmulatorKey(privateKey crypto.PrivateKey) { func (p *State) DeploymentContractsByNetwork(network config.Network) ([]*project.Contract, error) { contracts := make([]*project.Contract, 0) - // get deployments for the specified network + // get deployments for the specified network (explicit, no fallback) for _, deploy := range p.conf.Deployments.ByNetwork(network.Name) { account, err := p.accounts.ByName(deploy.Account) if err != nil { @@ -276,12 +276,27 @@ func (p *State) ContractAddress(contract *config.Contract, network config.Networ func (p *State) AliasesForNetwork(network config.Network) project.LocationAliases { aliases := make(project.LocationAliases) - // get all contracts for selected network and if any has an address as target make it an alias + // Determine fallback from network.Fork if provided (e.g., forked emulator should use mainnet aliases) + fallback := network.Fork + + // get all contracts for selected network; if alias not defined, try fallback for _, contract := range p.conf.Contracts { - if contract.IsAliased() && contract.Aliases.ByNetwork(network.Name) != nil { - alias := contract.Aliases.ByNetwork(network.Name).Address.String() - aliases[filepath.Clean(contract.Location)] = alias // alias for import by file location - aliases[contract.Name] = alias // alias for import by name + if !contract.IsAliased() { + continue + } + if a := contract.Aliases.ByNetwork(network.Name); a != nil { + addr := a.Address.String() + aliases[filepath.Clean(contract.Location)] = addr + aliases[contract.Name] = addr + continue + } + if fallback != "" { + if a := contract.Aliases.ByNetwork(fallback); a != nil { + addr := a.Address.String() + aliases[filepath.Clean(contract.Location)] = addr + aliases[contract.Name] = addr + continue + } } }