From 7dc1a3623c62a42a169639864ac938e7146b2877 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 24 Nov 2025 08:36:44 -0800 Subject: [PATCH 1/2] Add advanced fork mode import aliasing --- config/contract.go | 14 +++++++++ config/json/network.go | 22 ++++++++------ config/json/network_test.go | 4 +-- config/network.go | 1 + schema.json | 57 +++++++++---------------------------- state.go | 27 ++++++++++++++---- 6 files changed, 65 insertions(+), 60 deletions(-) diff --git a/config/contract.go b/config/contract.go index d04fd3ced..595398496 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 f6dda3395..b41a6d7bf 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 6cd24c681..28e916299 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 658a79f04..d247783a1 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 4f9b24536..47589209d 100644 --- a/schema.json +++ b/schema.json @@ -33,10 +33,7 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "address", - "keys" - ] + "required": ["address", "keys"] }, "advanceKey": { "properties": { @@ -78,9 +75,7 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "type" - ] + "required": ["type"] }, "advancedAccount": { "properties": { @@ -93,26 +88,19 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "address", - "key" - ] + "required": ["address", "key"] }, "advancedNetwork": { "properties": { "host": { "type": "string" }, - "key": { - "type": "string" - } + "key": { "type": "string" }, + "fork": { "type": "string" } }, "additionalProperties": false, "type": "object", - "required": [ - "host", - "key" - ] + "required": ["host"] }, "contractDeployment": { "properties": { @@ -126,10 +114,7 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "name", - "args" - ] + "required": ["name", "args"] }, "deployment": { "oneOf": [ @@ -202,10 +187,7 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "source", - "aliases" - ] + "required": ["source", "aliases"] }, "jsonContracts": { "patternProperties": { @@ -252,11 +234,7 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "source", - "hash", - "aliases" - ] + "required": ["source", "hash", "aliases"] }, "jsonDeployment": { "patternProperties": { @@ -288,10 +266,7 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "port", - "serviceAccount" - ] + "required": ["port", "serviceAccount"] }, "jsonEmulators": { "patternProperties": { @@ -330,10 +305,7 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "address", - "key" - ] + "required": ["address", "key"] }, "simpleAccountPre022": { "properties": { @@ -346,13 +318,10 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "address", - "keys" - ] + "required": ["address", "keys"] }, "simpleNetwork": { "type": "string" } } -} \ No newline at end of file +} diff --git a/state.go b/state.go index ee95b103f..70556a4ce 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 + } } } From 6cdaa3b14d56bd5a9f9c53d99410e51a92d56f89 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 24 Nov 2025 08:53:59 -0800 Subject: [PATCH 2/2] update schema --- schema.json | 58 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/schema.json b/schema.json index 47589209d..56cb2b931 100644 --- a/schema.json +++ b/schema.json @@ -33,7 +33,10 @@ }, "additionalProperties": false, "type": "object", - "required": ["address", "keys"] + "required": [ + "address", + "keys" + ] }, "advanceKey": { "properties": { @@ -75,7 +78,9 @@ }, "additionalProperties": false, "type": "object", - "required": ["type"] + "required": [ + "type" + ] }, "advancedAccount": { "properties": { @@ -88,19 +93,25 @@ }, "additionalProperties": false, "type": "object", - "required": ["address", "key"] + "required": [ + "address", + "key" + ] }, "advancedNetwork": { "properties": { "host": { "type": "string" }, - "key": { "type": "string" }, - "fork": { "type": "string" } + "key": { + "type": "string" + }, + "fork": { + "type": "string" + } }, "additionalProperties": false, - "type": "object", - "required": ["host"] + "type": "object" }, "contractDeployment": { "properties": { @@ -114,7 +125,10 @@ }, "additionalProperties": false, "type": "object", - "required": ["name", "args"] + "required": [ + "name", + "args" + ] }, "deployment": { "oneOf": [ @@ -187,7 +201,10 @@ }, "additionalProperties": false, "type": "object", - "required": ["source", "aliases"] + "required": [ + "source", + "aliases" + ] }, "jsonContracts": { "patternProperties": { @@ -234,7 +251,11 @@ }, "additionalProperties": false, "type": "object", - "required": ["source", "hash", "aliases"] + "required": [ + "source", + "hash", + "aliases" + ] }, "jsonDeployment": { "patternProperties": { @@ -266,7 +287,10 @@ }, "additionalProperties": false, "type": "object", - "required": ["port", "serviceAccount"] + "required": [ + "port", + "serviceAccount" + ] }, "jsonEmulators": { "patternProperties": { @@ -305,7 +329,10 @@ }, "additionalProperties": false, "type": "object", - "required": ["address", "key"] + "required": [ + "address", + "key" + ] }, "simpleAccountPre022": { "properties": { @@ -318,10 +345,13 @@ }, "additionalProperties": false, "type": "object", - "required": ["address", "keys"] + "required": [ + "address", + "keys" + ] }, "simpleNetwork": { "type": "string" } } -} +} \ No newline at end of file